税込金額から消費税額を算出してみた

# 概要
2023年10月よりインボイス制度が開始されました。

それにより、これまで税込金額で取り扱われていたシステムに対して、
「消費税額」を計算する必要が出てきました。

ちょうど仕事柄、税込金額から消費税の計算をする必要ができましたので、
消費税の計算プログラムを作ってみました。

# 計算プログラム

```php

<?php

class Consumption_tax_target
{
    // 計算対象のデータを保持する配列
    private array $consumption_tax_target_list = [];

    public function __construct()
    {

    }

    /**
     * @param int $price
     * @param float $consumption_tax_rate
     *
     * @return void
     */
    public function add(int $price, float $consumption_tax_rate): void
    {
        $this->consumption_tax_target_list[] = [
            'target_consumption_tax_rate' => $consumption_tax_rate,
            'price' => $price,
        ];
    }

    /**
     * @return array
     */
    public function get(): array
    {
        return $this->consumption_tax_target_list;
    }
}


class Consumption_tax_group
{
    // 税率ベースの配列
    private array $consumption_tax_group = [];

    public function __construct()
    {
        $this->initialize();
    }

    /**
     * 税率ベースの配列を初期化する
     *
     * @return void
     */
    private function initialize(): void
    {
        foreach (Consumption_tax::RATES as $consumption_tax_rate) {
            $this->add_rate_group($consumption_tax_rate);
        }
    }

    /**
     * @param float $consumption_tax_rate
     *
     * @return void
     */
    private function add_rate_group(float $consumption_tax_rate): void
    {
        $this->consumption_tax_group[] = [
            'consumption_tax_rate' => $consumption_tax_rate,
            'total_price' => 0,
            'consumption_tax_price' => 0,
        ];
    }

    /**
     * @param float $target_consumption_tax_rate
     * @param int $price
     *
     * @return void
     */
    public function add_consumption_tax_group_price(float $target_consumption_tax_rate, int $price): void
    {
        $key = $this->get_consumption_tax_group_key($target_consumption_tax_rate);

        $this->consumption_tax_group[$key]['total_price'] += $price;
    }

    /**
     * @param float $target_consumption_tax_rate
     * @param int $price
     *
     * @return void
     */
    public function set_consumption_tax_price(float $target_consumption_tax_rate, int $price): void
    {
        $key = $this->get_consumption_tax_group_key($target_consumption_tax_rate);

        $this->consumption_tax_group[$key]['consumption_tax_price'] = $price;
    }

    /**
     * @param float $target_consumption_tax_rate
     *
     * @return int
     */
    private function get_consumption_tax_group_key(float $target_consumption_tax_rate): int
    {
        $key = array_search($target_consumption_tax_rate, array_column($this->consumption_tax_group, 'consumption_tax_rate'));

        // システム的に把握していない税率が指定された場合は動的に、税率グループを追加する
        if ($key === false) {
            $this->add_rate_group($target_consumption_tax_rate);
            $key = $this->get_consumption_tax_group_key($target_consumption_tax_rate);
        }

        return $key;
    }

    /**
     * @return array
     */
    public function get_consumption_tax_group(): array
    {
        return $this->consumption_tax_group;
    }

    /**
     * @param float $target_consumption_tax_rate
     *
     * @return int
     */
    public function get_target_consumption_tax_rate_price(float $target_consumption_tax_rate): int
    {
        $key = $this->get_consumption_tax_group_key($target_consumption_tax_rate);

       return $this->consumption_tax_group[$key]['consumption_tax_price'];
    }
}


class Consumption_tax_calculator
{
    private Consumption_tax_group $consumption_tax_group;
    private Consumption_tax_target $consumption_tax_target;

    private Consumption_tax_round_type_enum $round_type_enum;

    public function __construct(Consumption_tax_round_type_enum $round_type_enum)
    {
        $this->instantiation_of_consumption_tax_group();
        $this->instantiation_of_consumption_tax_target();

        $this->round_type_enum = $round_type_enum;
    }

    /**
     * @return void
     */
    private function instantiation_of_consumption_tax_group(): void
    {
        $this->consumption_tax_group = new Consumption_tax_group();
    }

    /**
     * @return void
     */
    private function instantiation_of_consumption_tax_target(): void
    {
        $this->consumption_tax_target = new Consumption_tax_target();
    }

    /**
     * @param int $price
     * @param float $consumption_tax_rate
     *
     * @return void
     */
    public function add_target(int $price, float $consumption_tax_rate): void
    {
        $this->consumption_tax_target->add($price, $consumption_tax_rate);

        $this->calculate_in_consumption_tax_group();
    }

    /**
     * 消費税の小数点を処理する。
     *
     * @param float $tax
     *
     * @return float
     */
    private function round_consumption_tax(float $tax): float
    {
        if ($this->round_type_enum->is_floor()) {
            return floor($tax);
        } elseif ($this->round_type_enum->is_ceil()) {
            return ceil($tax);
        } elseif ($this->round_type_enum->is_round()) {
            return round($tax);
        }
        return $tax;
    }

    /**
     * 金額から内税を算出して返却する
     *
     * @param integer $price
     * @param float $rate
     *
     * @return int
     */
    private function calculate_in_consumption_tax(int $price, float $rate): int
    {
        $rate_for_calc = (float)bcdiv($rate, 100, 2);
        $tax = (float)bcmul((float)bcdiv($price, (float)bcadd(1, $rate_for_calc, 2), 2), $rate_for_calc, 2);
        // NOTE: 外税の場合の計算 ($price * $rate_for_calc)
        return $this->round_consumption_tax($tax);
    }

    /**
     * @return void
     */
    private function calculate_in_consumption_tax_group(): void
    {
        $this->instantiation_of_consumption_tax_group();
        foreach ($this->get_consumption_tax_target() as $item) {
            $this->consumption_tax_group->add_consumption_tax_group_price($item['target_consumption_tax_rate'], $item['price']);
        }

        // 消費税率ごとの消費税を算出する
        foreach ($this->get_consumption_tax_group() as $item) {
            $consumption_tax_price = $this->calculate_in_consumption_tax($item['total_price'], $item['consumption_tax_rate']);

            $this->consumption_tax_group->set_consumption_tax_price($item['consumption_tax_rate'], $consumption_tax_price);
        }
    }

    /**
     * 全消費税率の消費税額を取得する
     *
     * @return int
     */
    public function get_summary_of_consumption_tax_price(): int
    {
        $consumption_tax_price = 0;
        foreach ($this->get_consumption_tax_group() as $item) {
            $consumption_tax_price += $item['consumption_tax_price'];
        }

        return $consumption_tax_price;
    }

    /**
     * @return array
     */
    public function get_consumption_tax_group(): array
    {
        return $this->consumption_tax_group->get_consumption_tax_group();
    }

    /**
     * @return array
     */
    public function get_consumption_tax_target(): array
    {
        return $this->consumption_tax_target->get();
    }

    /**
     * @param float $target_consumption_tax_rate
     *
     * @return int
     */
    public function get_target_consumption_tax_rate_price(float $target_consumption_tax_rate): int
    {
        return $this->consumption_tax_group->get_target_consumption_tax_rate_price($target_consumption_tax_rate);
    }
}

class Consumption_tax_round_type_enum
{
    private int $scalar;
    
    /** 切り捨て */
    public const FLOOR = 1;
    /** 切り上げ */
    public const CEIL  = 2;
    /** 四捨五入 */
    public const ROUND = 3;

    public const LABELS = [
        self::FLOOR => '切り捨て',
        self::CEIL  => '切り上げ',
        self::ROUND => '四捨五入',
    ];

    public function __construct(int $value)
    {
        $this->scalar = $value;
    }

    public function value(): int
    {
        return $this->scalar;
    }
    
    /**
     * @return integer
     */
    public static function get_default(): int
    {
        return self::FLOOR;
    }

    /**
     * @return array
     */
    public static function values(): array
    {
        return [ self::FLOOR, self::CEIL, self::ROUND ];
    }

    /**
     * 切り捨てである
     *
     * @return boolean
     */
    public function is_floor(): bool
    {
        return $this->value() == self::FLOOR;
    }

    /**
     * 切り上げである
     *
     * @return boolean
     */
    public function is_ceil(): bool
    {
        return $this->value() == self::CEIL;
    }

    /**
     * 四捨五入である
     *
     * @return boolean
     */
    public function is_round(): bool
    {
        return $this->value() == self::ROUND;
    }
}


final class Consumption_tax
{
    /** 消費税コード：非課税 */
    public const NONE = 0;
    /** 消費税コード：標準税率 */
    public const STANDARD = 1;
    /** 消費税コード：軽減税率 */
    public const REDUCED = 2;

    /** 消費税率 (パーセント) */
    public const RATES = [
        self::STANDARD => 10,
        self::REDUCED  => 8,
        self::NONE     => 0,
    ];
}


```

# 動かすコード

```php

<?php

$round_type = new Consumption_tax_round_type_enum(Consumption_tax_round_type_enum::ROUND);
$consumption_tax_calculator = new Consumption_tax_calculator($round_type);
$consumption_tax_calculator->add_target(1000, 0);
$consumption_tax_calculator->add_target(10, 0);
$consumption_tax_calculator->add_target(2000, 10);
$consumption_tax_calculator->add_target(20, 10);
$consumption_tax_calculator->add_target(3000, 8);
$consumption_tax_calculator->add_target(30, 8);
$consumption_tax_calculator->add_target(1000, 3.5);

$consumption_tax_price = $consumption_tax_calculator->get_summary_of_consumption_tax_price();

echo $consumption_tax_price.PHP_EOL;
// 437

```

# 購入リスト
|No|商品名|税込金額|税率|
|---|---|---|---|
|1|鉛筆10本|1,000円|0%|
|2|鉛筆1本|10円|0%|
|3|消しゴム10個|2,000円|10%|
|4|消しゴム1個|20円|10%|
|5|りんご10個|3,000円|8%|
|6|りんご1個|30円|8%|
|7|スマイル|1,000円|3.5%|