diff --git a/data/Smarty/templates/default/mail_templates/order_mail.tpl b/data/Smarty/templates/default/mail_templates/order_mail.tpl index b58cd99a86..5d9d58ae6e 100644 --- a/data/Smarty/templates/default/mail_templates/order_mail.tpl +++ b/data/Smarty/templates/default/mail_templates/order_mail.tpl @@ -56,7 +56,7 @@ ------------------------------------------------- -小 計 ¥ (うち消費税 ¥) +小 計 ¥ 値引き ¥ diff --git a/data/Smarty/templates/mobile/mail_templates/order_mail.tpl b/data/Smarty/templates/mobile/mail_templates/order_mail.tpl index 84de76be3d..ef2806f60a 100644 --- a/data/Smarty/templates/mobile/mail_templates/order_mail.tpl +++ b/data/Smarty/templates/mobile/mail_templates/order_mail.tpl @@ -49,7 +49,7 @@ -小 計 ¥ (うち消費税 ¥) +小 計 ¥ 値引き ¥ diff --git a/data/class/SC_Fpdf.php b/data/class/SC_Fpdf.php index 9a2b57af16..fb7995fc13 100644 --- a/data/class/SC_Fpdf.php +++ b/data/class/SC_Fpdf.php @@ -155,6 +155,11 @@ private function setShopData() //ロゴ画像 $logo_file = PDF_TEMPLATE_REALDIR . 'logo.png'; $this->Image($logo_file, 124, 46, 40); + + if (defined('INVOICE_REGISTRATION_NUM')) { + $text = '登録番号: '.INVOICE_REGISTRATION_NUM; + $this->lfText(125, 87, $text, 8); + } } private function setMessageData() @@ -228,6 +233,8 @@ private function setOrderData() $monetary_unit = '円'; $point_unit = 'Pt'; + $arrTaxableTotal = []; + $defaultTaxRule = SC_Helper_TaxRule_Ex::getTaxRule(); // 購入商品情報 for ($i = 0; $i < count($this->arrDisp['quantity']); $i++) { // 購入数量 @@ -249,9 +256,18 @@ private function setOrderData() $arrOrder[$i][0] .= ' * '.$this->arrDisp['classcategory_name2'][$i].' ]'; } } + + // 標準税率より低い税率は軽減税率として※を付与 + if ($this->arrDisp['tax_rate'][$i] < $defaultTaxRule['tax_rate']) { + $arrOrder[$i][0] .= ' ※'; + } $arrOrder[$i][1] = number_format($data[0]); $arrOrder[$i][2] = number_format($data[1]).$monetary_unit; $arrOrder[$i][3] = number_format($data[2]).$monetary_unit; + if (array_key_exists($this->arrDisp['tax_rate'][$i], $arrTaxableTotal) === false) { + $arrTaxableTotal[$this->arrDisp['tax_rate'][$i]] = 0; + } + $arrTaxableTotal[$this->arrDisp['tax_rate'][$i]] += $data[2]; } $arrOrder[$i][0] = ''; @@ -270,18 +286,21 @@ private function setOrderData() $arrOrder[$i][1] = ''; $arrOrder[$i][2] = '送料'; $arrOrder[$i][3] = number_format($this->arrDisp['deliv_fee']).$monetary_unit; + $arrTaxableTotal[intval($defaultTaxRule['tax_rate'])] += $this->arrDisp['deliv_fee']; $i++; $arrOrder[$i][0] = ''; $arrOrder[$i][1] = ''; $arrOrder[$i][2] = '手数料'; $arrOrder[$i][3] = number_format($this->arrDisp['charge']).$monetary_unit; + $arrTaxableTotal[intval($defaultTaxRule['tax_rate'])] += $this->arrDisp['charge']; $i++; $arrOrder[$i][0] = ''; $arrOrder[$i][1] = ''; $arrOrder[$i][2] = '値引き'; - $arrOrder[$i][3] = '- '.number_format(($this->arrDisp['use_point'] * POINT_VALUE) + $this->arrDisp['discount']).$monetary_unit; + $discount_total = ($this->arrDisp['use_point'] * POINT_VALUE) + $this->arrDisp['discount']; + $arrOrder[$i][3] = '- '.number_format($discount_total).$monetary_unit; $i++; $arrOrder[$i][0] = ''; @@ -311,6 +330,22 @@ private function setOrderData() } $this->FancyTable($this->label_cell, $arrOrder, $this->width_cell); + + $this->SetLineWidth(.3); + $this->SetFont('SJIS', '', 6); + + $this->Cell(0, 0, '', 0, 1, 'C', 0, ''); + // 行頭近くの場合、表示崩れがあるためもう一個字下げする + if (270 <= $this->GetY()) { + $this->Cell(0, 0, '', 0, 1, 'C', 0, ''); + } + $width = array_reduce($this->width_cell, function ($n, $w) { + return $n + $w; + }); + $this->SetX(20); + + $message = SC_Helper_TaxRule_Ex::getTaxDetail($arrTaxableTotal, $discount_total); + $this->MultiCell($width, 4, $message, 0, 'R', ''); } /** diff --git a/data/class/helper/SC_Helper_TaxRule.php b/data/class/helper/SC_Helper_TaxRule.php index d01b50b54f..b0b5324097 100644 --- a/data/class/helper/SC_Helper_TaxRule.php +++ b/data/class/helper/SC_Helper_TaxRule.php @@ -62,6 +62,85 @@ public static function sfTax($price, $product_id = 0, $product_class_id = 0, $pr return SC_Helper_TaxRule_Ex::calcTax($price, $arrTaxRule['tax_rate'], $arrTaxRule['tax_rule'], $arrTaxRule['tax_adjust']); } + /** + * 消費税の内訳を返す. + * + * 税率ごとに以下のような連想配列を返す. + * - discount: 税率毎の値引額 + * - total: 値引後の税込み合計金額 + * - tax: 値引後の税額 + * 値引額合計は税率ごとに按分する. + * 課税規則は標準税率の設定を使用する. + * + * @param array{8?:int, 10?:int} $arrTaxableTotal 税率ごとのお支払い合計金額 + * @param int $discount_total 値引額合計 + * @return array{8?:array{discount;int,total:int,tax:int}, 10?:array{discount;int,total:int,tax:int}} + */ + public static function getTaxPerTaxRate($arrTaxableTotal, $discount_total = 0) + { + $arrDefaultTaxRule = static::getTaxRule(); + + ksort($arrTaxableTotal); + $cf_discount = 0; + $taxable_total = array_sum($arrTaxableTotal); + $divide = []; + $result = []; + + // 按分後の値引額の合計(8%対象商品の按分後の値引額 + 10%対象商品の按分後の値引額)が実際の値引額より+-1円となる事への対処 + // ①按分した値引き額を四捨五入で丸める + foreach ($arrTaxableTotal as $rate => $total) { + $discount[$rate] = $taxable_total !== 0 ? round(($discount_total * $total / $taxable_total), 0) : 0; + $divide[$rate] = [ + 'discount' => intval($discount[$rate]), + ]; + $cf_discount += $divide[$rate]['discount']; + } + // ②四捨五入したとしても、四捨五入前の値引額がそれぞれ 16.5 + 75.5 の場合 →(四捨五入端数処理)→ 17 + 76 両方繰り上がる。事への対処 + $defaultTaxRule = $arrDefaultTaxRule['calc_rule']; + $diff = $discount_total - $cf_discount; + if ($diff > 0) { + $divide[$defaultTaxRule]['discount'] += $diff; + } elseif ($diff < 0) { + $divide[$defaultTaxRule]['discount'] -= $diff; + } + + foreach ($arrTaxableTotal as $rate => $total) { + if ($rate == $defaultTaxRule) { + $discount[$rate] = $divide[$defaultTaxRule]['discount']; + } else { + $discount[$rate] = $taxable_total !== 0 ? round(($discount_total * $total / $taxable_total), 0) : 0; + } + $reduced_total = $total - $discount[$rate]; + $tax = $reduced_total * ($rate / (100 + $rate)); + $result[$rate] = [ + 'discount' => intval($discount[$rate]), + 'total' => intval($reduced_total), + 'tax' => intval(static::roundByCalcRule($tax, $defaultTaxRule)), + ]; + } + return $result; + } + + /** + * 消費税の内訳の文字列を返す. + * + * 複数の税率がある場合は改行で区切る. + * + * @param array{8?:int, 10?:int} $arrTaxableTotal 税率ごとのお支払い合計金額 + * @param int $discount_total 値引額合計 + * @return string (<税率>%対象: <値引後税込合計>円 内消費税: <値引後税額>円) + */ + public static function getTaxDetail($arrTaxableTotal, $discount_total = 0) + { + $arrTaxPerTaxRate = static::getTaxPerTaxRate($arrTaxableTotal, $discount_total); + $result = ''; + foreach ($arrTaxPerTaxRate as $rate => $item) { + $result .= '('.$rate .'%対象: '. number_format($item['total']).'円 内消費税: '.number_format($item['tax']).'円)'.PHP_EOL; + } + + return $result; + } + /** * 設定情報IDに基づいて税金付与した金額を返す * (受注データのようにルールが決まっている場合用) diff --git a/tests/class/helper/SC_Helper_TaxRule/SC_Helper_TaxRule_TestBase.php b/tests/class/helper/SC_Helper_TaxRule/SC_Helper_TaxRule_TestBase.php index a721eaf5ed..22691105d3 100644 --- a/tests/class/helper/SC_Helper_TaxRule/SC_Helper_TaxRule_TestBase.php +++ b/tests/class/helper/SC_Helper_TaxRule/SC_Helper_TaxRule_TestBase.php @@ -28,7 +28,7 @@ * @author Nobuhiko Kimoto * @version $Id$ */ -class SC_Helper_TaxRule_TestBase extends Common_TestCase +abstract class SC_Helper_TaxRule_TestBase extends Common_TestCase { /** diff --git a/tests/class/helper/SC_Helper_TaxRule/SC_Helper_TaxRule_getTaxDetailTest.php b/tests/class/helper/SC_Helper_TaxRule/SC_Helper_TaxRule_getTaxDetailTest.php new file mode 100644 index 0000000000..be84e71581 --- /dev/null +++ b/tests/class/helper/SC_Helper_TaxRule/SC_Helper_TaxRule_getTaxDetailTest.php @@ -0,0 +1,368 @@ +setUpTaxRule([ + [ + 'tax_rule_id' => 1004, + 'apply_date' => '2019-10-01 00:00:00', + 'tax_rate' => '10', + 'calc_rule' => '1', + 'product_id' => '0', + 'product_class_id' => '0', + 'del_flg' => '0', + 'member_id' => 1, + 'create_date' => '2000-01-01 00:00:00', + 'update_date' => '2000-01-01 00:00:00', + ], + ]); + + $arrTaxableTotal = [ + 10 => 724431, + 8 => 65756, + ]; + $discount_total = 7159; + + $actual = SC_Helper_TaxRule_Ex::getTaxPerTaxRate($arrTaxableTotal, $discount_total); + self::assertSame( + [ + 8 => [ + 'discount' => 596, + 'total' => 65160, + 'tax' => 4827 + ], + 10 => [ + 'discount' => 6563, + 'total' => 717868, + 'tax' => 65261 + ] + ], + $actual + ); + + self::assertSame( + '(8%対象: 65,160円 内消費税: 4,827円)'.PHP_EOL. + '(10%対象: 717,868円 内消費税: 65,261円)'.PHP_EOL, + SC_Helper_TaxRule_Ex::getTaxDetail($arrTaxableTotal, $discount_total) + ); + } + + public function testGetTaxPerTaxRateWithZero() + { + $this->setUpTaxRule([ + [ + 'tax_rule_id' => 1004, + 'apply_date' => '2019-10-01 00:00:00', + 'tax_rate' => '10', + 'calc_rule' => '1', + 'product_id' => '0', + 'product_class_id' => '0', + 'del_flg' => '0', + 'member_id' => 1, + 'create_date' => '2000-01-01 00:00:00', + 'update_date' => '2000-01-01 00:00:00', + ], + ]); + + $arrTaxableTotal = [ + 10 => 0, + 8 => 0, + ]; + $discount_total = 0; + + $actual = SC_Helper_TaxRule_Ex::getTaxPerTaxRate($arrTaxableTotal, $discount_total); + self::assertSame( + [ + 8 => [ + 'discount' => 0, + 'total' => 0, + 'tax' => 0 + ], + 10 => [ + 'discount' => 0, + 'total' => 0, + 'tax' => 0 + ] + ], + $actual + ); + + self::assertSame( + '(8%対象: 0円 内消費税: 0円)'.PHP_EOL. + '(10%対象: 0円 内消費税: 0円)'.PHP_EOL, + SC_Helper_TaxRule_Ex::getTaxDetail($arrTaxableTotal, $discount_total) + ); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testGetTaxPerTaxRateWithFloor() + { + self::markTestSkipped('Skip this test because @runInSeparateProcess does not work properly'); + + $this->setUpTaxRule([ + [ + 'tax_rule_id' => 1004, + 'apply_date' => '2019-10-01 00:00:00', + 'tax_rate' => '10', + 'calc_rule' => '2', // floor + 'product_id' => '0', + 'product_class_id' => '0', + 'del_flg' => '0', + 'member_id' => 1, + 'create_date' => '2000-01-01 00:00:00', + 'update_date' => '2000-01-01 00:00:00', + ], + ]); + + $arrTaxableTotal = [ + 10 => 724431, + 8 => 65756, + ]; + $discount_total = 7159; + + $actual = SC_Helper_TaxRule_Ex::getTaxPerTaxRate($arrTaxableTotal, $discount_total); + self::assertSame( + [ + 8 => [ + 'discount' => 596, + 'total' => 65160, + 'tax' => 4826 + ], + 10 => [ + 'discount' => 6563, + 'total' => 717868, + 'tax' => 65260 + ] + ], + $actual + ); + + self::assertSame(array_sum($arrTaxableTotal) - $discount_total, $actual[8]['total'] + $actual[10]['total']); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testGetTaxPerTaxRateWithCeil() + { + self::markTestSkipped('Skip this test because @runInSeparateProcess does not work properly'); + + $this->setUpTaxRule([ + [ + 'tax_rule_id' => 1004, + 'apply_date' => '2019-10-01 00:00:00', + 'tax_rate' => '10', + 'calc_rule' => '3', // ceil + 'product_id' => '0', + 'product_class_id' => '0', + 'del_flg' => '0', + 'member_id' => 1, + 'create_date' => '2000-01-01 00:00:00', + 'update_date' => '2000-01-01 00:00:00', + ], + ]); + + $arrTaxableTotal = [ + 10 => 724431, + 8 => 65756, + ]; + $discount_total = 7159; + + $actual = SC_Helper_TaxRule_Ex::getTaxPerTaxRate($arrTaxableTotal, $discount_total); + self::assertSame( + [ + 8 => [ + 'discount' => 596, + 'total' => 65160, + 'tax' => 4827 + ], + 10 => [ + 'discount' => 6563, + 'total' => 717868, + 'tax' => 65261 + ] + ], + $actual + ); + + self::assertSame(array_sum($arrTaxableTotal) - $discount_total, $actual[8]['total'] + $actual[10]['total']); + } + + /** + * @see https://github.com/EC-CUBE/ec-cube2/pull/762#issuecomment-1897799676 + */ + public function testGetTaxPerTaxRateWithRound2() + { + $this->setUpTaxRule([ + [ + 'tax_rule_id' => 1004, + 'apply_date' => '2019-10-01 00:00:00', + 'tax_rate' => '10', + 'calc_rule' => '1', + 'product_id' => '0', + 'product_class_id' => '0', + 'del_flg' => '0', + 'member_id' => 1, + 'create_date' => '2000-01-01 00:00:00', + 'update_date' => '2000-01-01 00:00:00', + ], + ]); + + $arrTaxableTotal = [ + 10 => 1595, + 8 => 7398, + ]; + $discount_total = 92; + + $actual = SC_Helper_TaxRule_Ex::getTaxPerTaxRate($arrTaxableTotal, $discount_total); + self::assertSame( + [ + 8 => [ + 'discount' => 76, + 'total' => 7322, + 'tax' => 542 + ], + 10 => [ + 'discount' => 16, + 'total' => 1579, + 'tax' => 144 + ] + ], + $actual + ); + + self::assertSame( + '(8%対象: 7,322円 内消費税: 542円)'.PHP_EOL. + '(10%対象: 1,579円 内消費税: 144円)'.PHP_EOL, + SC_Helper_TaxRule_Ex::getTaxDetail($arrTaxableTotal, $discount_total) + ); + + self::assertSame(array_sum($arrTaxableTotal) - $discount_total, $actual[8]['total'] + $actual[10]['total']); + } + + /** + * @see https://github.com/EC-CUBE/ec-cube2/pull/762#issuecomment-1897799676 + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testGetTaxPerTaxRateWithFloor2() + { + self::markTestSkipped('Skip this test because @runInSeparateProcess does not work properly'); + + $this->setUpTaxRule([ + [ + 'tax_rule_id' => 1004, + 'apply_date' => '2019-10-01 00:00:00', + 'tax_rate' => '10', + 'calc_rule' => '2', // floor + 'product_id' => '0', + 'product_class_id' => '0', + 'del_flg' => '0', + 'member_id' => 1, + 'create_date' => '2000-01-01 00:00:00', + 'update_date' => '2000-01-01 00:00:00', + ], + ]); + + $arrTaxableTotal = [ + 10 => 1595, + 8 => 7398, + ]; + $discount_total = 92; + + $actual = SC_Helper_TaxRule_Ex::getTaxPerTaxRate($arrTaxableTotal, $discount_total); + self::assertSame( + [ + 8 => [ + 'discount' => 76, + 'total' => 7322, + 'tax' => 542 + ], + 10 => [ + 'discount' => 16, + 'total' => 1579, + 'tax' => 143 + ] + ], + $actual + ); + + self::assertSame(array_sum($arrTaxableTotal) - $discount_total, $actual[8]['total'] + $actual[10]['total']); + } + + /** + * @see https://github.com/EC-CUBE/ec-cube2/pull/762#issuecomment-1897799676 + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testGetTaxPerTaxRateWithCeil2() + { + self::markTestSkipped('Skip this test because @runInSeparateProcess does not work properly'); + + $this->setUpTaxRule([ + [ + 'tax_rule_id' => 1004, + 'apply_date' => '2019-10-01 00:00:00', + 'tax_rate' => '10', + 'calc_rule' => '3', // ceil + 'product_id' => '0', + 'product_class_id' => '0', + 'del_flg' => '0', + 'member_id' => 1, + 'create_date' => '2000-01-01 00:00:00', + 'update_date' => '2000-01-01 00:00:00', + ], + ]); + + $arrTaxableTotal = [ + 10 => 1595, + 8 => 7398, + ]; + $discount_total = 92; + + $actual = SC_Helper_TaxRule_Ex::getTaxPerTaxRate($arrTaxableTotal, $discount_total); + self::assertSame( + [ + 8 => [ + 'discount' => 76, + 'total' => 7322, + 'tax' => 543 + ], + 10 => [ + 'discount' => 16, + 'total' => 1579, + 'tax' => 144 + ] + ], + $actual + ); + + self::assertSame(array_sum($arrTaxableTotal) - $discount_total, $actual[8]['total'] + $actual[10]['total']); + } + + protected function setUpTaxRule(array $taxs = []) + { + $this->objQuery->delete('dtb_tax_rule'); + foreach ($taxs as $key => $item) { + $this->objQuery->insert('dtb_tax_rule', $item); + } + } +}