app/Customize/Service/EstimateCharacterizer.php line 1289

Open in your IDE?
  1. <?php
  2. namespace Customize\Service;
  3. use Customize\Converter\EstimateConverter;
  4. use Eccube\Entity\Order;
  5. use Eccube\Repository\PaymentRepository;
  6. use Customize\Service\CartService;
  7. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  8. use Eccube\Repository\DeliveryRepository;
  9. use Eccube\Repository\DeliveryFeeRepository;
  10. class EstimateCharacterizer
  11. {
  12.     const DeliveryId 1#佐川 加工
  13.     const EstimateType = [=> 'print'=> 'susoage'=> 'charge'10 => 'gross'];
  14.     const OptionTotalColumn = ['product_total''quantity''white_only'];
  15.     const ICHI_OTHER = [
  16.         'ichi_id' => 0,
  17.         'ichi_name' => 'その他',
  18.         'ichi_display' => null,
  19.     ];
  20.     /**
  21.      * @var CommonService
  22.      */
  23.     protected $CommonService;
  24.     /**
  25.      * @var EstimateConverter
  26.      */
  27.     protected $EstimateConverter;
  28.     /**
  29.      * @var LmDeliveryFee
  30.      */
  31.     protected $LmDeliveryFee;
  32.     /**
  33.      * @var PaymentRepository
  34.      */
  35.     protected $PaymentRepository;
  36.     /**
  37.      * @var TaxRuleService
  38.      */
  39.     protected $TaxRuleService;
  40.     /**
  41.      * @var string[]
  42.      */
  43.     protected $PrintName;
  44.     /**
  45.      * @var string[]
  46.      */
  47.     protected $EstimateText;
  48.     /**
  49.      * @var array
  50.      */
  51.     protected $EstimateDetailList;
  52.     /**
  53.      * @var int
  54.      */
  55.     protected $Ritu 0;
  56.     /**
  57.      * @var int
  58.      */
  59.     protected $LargeDiscount 0;
  60.     protected $RepeatDate;
  61.     protected $CartService;
  62.     /**
  63.      * キャンペーン割引合計
  64.      * @var int
  65.      */
  66.     protected $CampaignDiscount 0;
  67.     /**
  68.      * キャンペーン割引明細
  69.      * @var array
  70.      */
  71.     protected $CampaignDetails = [];
  72.     /**
  73.      * 直近のカートアイテム
  74.      * @var array
  75.      */
  76.     protected $LastCartItems = [];
  77.     /**
  78.      * @var DeliveryRepository
  79.      */
  80.     protected $DeliveryRepository;
  81.     /**
  82.      * @var DeliveryFeeRepository
  83.      */
  84.     protected $DeliveryFeeRepository;
  85.     /**
  86.      * @var YamlService
  87.      */
  88.     protected $YamlService;
  89.     /**
  90.      * @var array
  91.      */
  92.     private $Characters = [];
  93.     /**
  94.      * @var int
  95.      */
  96.     private $LotBaseQuantity 0;
  97.     /**
  98.      * @var array
  99.      */
  100.     private $LotBaseQuantityByKind = [];
  101.     /**
  102.      * @var array
  103.      */
  104.     private $LotBaseQuantityByKindAndType = [];
  105.     /**
  106.      * @var int
  107.      */
  108.     private $LotQtyPrint 0;
  109.     private $LotQtyMagic 0;
  110.     private $LotQtyShishu 0;
  111.     private $LotQtyTensha 0;
  112.     /**
  113.      * @var array
  114.      */
  115.     private $LotKindAlreadyShown = [];
  116.     /**
  117.      * @var array
  118.      */
  119.     private $ApplyLotAllowedKind = [];
  120.     /**
  121.      * @var string
  122.      */
  123.     protected $Page;
  124.     /**
  125.      * @var int
  126.      */
  127.     protected $EstimateLine;
  128.     /**
  129.      * @var string
  130.      */
  131.     protected $EstimateType;
  132.     /**
  133.      * @var int
  134.      */
  135.     protected $Quantity;
  136.     public function __construct(CommonService     $CommonService,
  137.                                 EstimateConverter $estimateConverter,
  138.                                 LmDeliveryFee     $lmDeliveryFee,
  139.                                 PaymentRepository $paymentRepository,
  140.                                 TaxRuleService    $taxRuleService,
  141.                                 CartService $CartService,
  142.                                 DeliveryRepository $DeliveryRepository,
  143.                                 DeliveryFeeRepository $DeliveryFeeRepository,
  144.                                 YamlService $YamlService
  145.                                 )
  146.     {
  147.         $this->CommonService $CommonService;
  148.         $this->EstimateConverter $estimateConverter;
  149.         $this->LmDeliveryFee $lmDeliveryFee;
  150.         $this->PaymentRepository $paymentRepository;
  151.         $this->TaxRuleService $taxRuleService;
  152.         $this->PrintName $CommonService->GetConfig('PRINT_NAME');
  153.         $this->EstimateText $CommonService->GetConfig('ESTIMATE_TEXT');
  154.         $this->EstimateDetailList $CommonService->GetConfig('ESTIMATE_DETAIL_LIST');
  155.         $this->CartService   $CartService;
  156.         $this->DeliveryRepository $DeliveryRepository;
  157.         $this->DeliveryFeeRepository $DeliveryFeeRepository;
  158.         $this->YamlService $YamlService;
  159.     }
  160.     /********************************************************************************************************* */
  161.     /**
  162.      * 見積もりシミュレーション文字化
  163.      *
  164.      */
  165.     public function EstimateCharacterIzation($Options$OptionTotal = [], $Page null)
  166.     {
  167.         $this->Characters = [];
  168.         if (!is_array($Options)) {
  169.             $Options json_decode($Optionstrue);
  170.         }
  171.         // $Options = $this->SetOptions($Options, $OptionTotal);
  172.         if ($this->CommonService->IsNull($OptionTotal)) {
  173.             return;
  174.         }
  175.         $this->Page $Page;
  176.         if (!isset($Options['product_total'])) {
  177.             if (!is_null($Options) && count($Options) > 0) {
  178.                 $Options $Options[0];
  179.             } else {
  180.                 return;
  181.             }
  182.         }
  183.         $this->Characters['product_total'] = $Options['product_total'];
  184.         #プリントの算出
  185.         if ($Error $this->GetPrint($Options)) {
  186.             throw new \RuntimeException("加工内容の取得処理に失敗しました。\nエラーコード: {$Error}");
  187.         }
  188.         #裾上げ
  189.         if ($Error $this->GetSusoage($Options)) {
  190.             throw new \RuntimeException("加工内容(裾上げ)の取得処理に失敗しました。\nエラーコード: {$Error}");
  191.         }
  192.         #配送費の抽出
  193.         if ($Error $this->GetCharge($Options)) {
  194.             throw new \RuntimeException("配送費の取得処理に失敗しました。\nエラーコード: {$Error}");
  195.         }
  196.         return;
  197.     }
  198.     /**
  199.      *
  200.      * 離島 043-1400
  201.      */
  202.     protected function GetCharge($Options)
  203.     {
  204.         if ($this->Page) {
  205.             return;
  206.         }
  207.         $ProductTotal $Options['product_total'];
  208.         $this->SetEstimateType(9);
  209.         $this->EstimateLine 1;
  210.         $this->SetCharacters('title'$this->EstimateText['Text31']);
  211.         $this->SetCharacters('total'$this->EstimateText['Text32']);
  212.         $this->SetCharacters('service'false);
  213.         if (!$Charge $Options['charge'] ?? null) {
  214.             return;
  215.         }
  216.         $Total $ProductTotal + ($this->Characters['print_total']['total'] ?? 0)
  217.                                + ($this->Characters['susoage']['total'] ?? 0);
  218.         $ChargeTotal 0;
  219.         if ($Prefe $Charge['prefecture'] ?? null) {
  220.             #デリバリーFeeの計算
  221.             $DeliveryFee $this->DeliveryFeeRepository->findOneBy([
  222.                 'Delivery' => self::DeliveryId,
  223.                 'Pref' => $Prefe,
  224.             ]);
  225.             $Fee is_object($DeliveryFee) ? $DeliveryFee->getFee() : 0;
  226.             $this->SetCharacters('total'Value$Fee);
  227.             $ChargeTotal += $Fee;
  228.             #中継料
  229.             $this->EstimateLine 2;
  230.             $RelayFee 0;
  231.             if ($PostCode $Charge['postcode'] ?? null) {
  232.                 $RelayFee $this->LmDeliveryFee->Island($PostCode);
  233.             }
  234.             #離島・沖縄は値引きしない;
  235.             if (=== $this->getDelivery($Options['product_class_id'], $Options) && $RelayFee) {
  236.                 $this->SetCharacters('title'$this->EstimateText['Text33']);
  237.                 $this->SetCharacters('total'$RelayFee);
  238.                 $this->SetCharacters('service'false);
  239.                 $ChargeTotal += $RelayFee;
  240.             } else if ($Total >= $this->CommonService->GetBaseInfo('delivery_free_amounte') && 47 != $Prefe) {
  241.                 #送料無料(金額)を超えている & 道府県が沖縄ではない
  242.                 $this->SetCharacters('title'$this->EstimateText['Text34']);
  243.                 $this->SetCharacters('total'$Fee * -1);
  244.                 $this->SetCharacters('service'true);
  245.                 $ChargeTotal -= $Fee;
  246.             }
  247.         }
  248.         #支払い手数料
  249.         if ($PaymentId $Charge['payment'] ?? null) {
  250.             $this->EstimateLine 3;
  251.             $Payment $this->PaymentRepository->find($PaymentId);
  252.             $PaymentName $Payment->getMethod();
  253.             $Fee $Payment->getCharge();
  254.             if ($Fee 0) {
  255.                 $this->SetCharacters('title'$PaymentName $this->EstimateText['Text35']);
  256.                 $this->SetCharacters('total'$Fee);
  257.                 $this->SetCharacters('service'false);
  258.                 $ChargeTotal += $Fee;
  259.                 //支払い手数料があった場合
  260.                 if ($Total >= $this->CommonService->GetConfig('Payment_Charge_free')) {
  261.                     $this->EstimateLine 4;
  262.                     $this->SetCharacters('title'$PaymentName $this->EstimateText['Text36']);
  263.                     $this->SetCharacters('total'$Fee * -1);
  264.                     $this->SetCharacters('service'true);
  265.                     $ChargeTotal -= $Fee;
  266.                 }
  267.             }
  268.         }#支払い手数料
  269.         $lot $this->Characters['lot']['total'] ?? ;
  270.         $this->EstimateLine 0;
  271.         $Re['title'] = $this->EstimateText['Text09'];
  272.         $Re['total'] = $ChargeTotal $lot ;
  273.         $this->SetCharacters('total'$Re);
  274.         #大口割引
  275.         $this->SetLargeDiscount($Total);
  276.         if ($this->LargeDiscount) {
  277.             $Re['title'] = $this->EstimateText['Text37'] . "({$this->Ritu}%)";
  278.             $Re['total'] = $this->LargeDiscount * -1;
  279.             $Re['title2'] = $this->EstimateText['Text09'];
  280.             $this->Characters['lage_discount'] = $Re;
  281.         }
  282.         // キャンペーン割引(よりどりみどり割 / まとめ割)
  283.         if ($this->CampaignDiscount 0) {
  284.             $Cd = [];
  285.             $Cd['title'] = 'キャンペーン割引';
  286.             $Cd['total'] = $this->CampaignDiscount * -1;
  287.             if (!empty($this->CampaignDetails)) {
  288.                 $Cd['detail'] = $this->CampaignDetails;
  289.             }
  290.             $this->Characters['campaign_discount'] = $Cd;
  291.         } 
  292.         #総計の計算
  293.         $this->SetEstimateType(10);
  294.         $Gross $Total $ChargeTotal $this->LargeDiscount +  $lot;
  295.         $this->EstimateLine 1;
  296.         $this->SetCharacters('title'$this->EstimateText['Text38']);
  297.         $this->SetCharacters('total'$Gross);
  298.         $this->EstimateLine 2;
  299.         $this->SetCharacters('title'$this->EstimateText['Text39']);
  300.         $this->SetCharacters('total'$One floor($Gross $Options['quantity']));
  301.     }
  302.     protected function SetEstimateType($Num)
  303.     {
  304.         $this->EstimateType self::EstimateType[$Num];
  305.         $this->EstimateLine 0;
  306.     }
  307.     protected function SetItiName($ItiName)
  308.     {
  309.         return preg_replace('/[A-Z]|:/'''$ItiName);
  310.     }
  311.     /**
  312.      * 1 最大値のロットを表示
  313.      * 2 ロットは加算されない
  314.      * 3 0か未設定の場合計算させる  
  315.      */
  316.     protected function SetLots($Lot$Kind null$PrintType null$isKyouei false)
  317.     {
  318.         if (!is_null($Kind)) {
  319.             // この明細で許可されていない種別はスキップ
  320.             if (!($this->ApplyLotAllowedKind[$Kind] ?? false)) { return; }
  321.             
  322.             // Create unique key based on Kind and PrintType logic
  323.             $lotKey $this->createLotKey($Kind$PrintType);
  324.             // 既に表示済みの種別+タイプはスキップ(lotの重複表示を防ぐ)
  325.             if (($this->LotKindAlreadyShown[$lotKey] ?? false)) { return; }
  326.             
  327.             // 種別単位で厳密に判定(未集計のKindは適用しない)
  328.             if (!array_key_exists($Kind$this->LotBaseQuantityByKind)) { return; }
  329.             // Get base quantity based on Kind and PrintType combination
  330.             $baseQuantity $this->getBaseQuantityByKindAndType($Kind$PrintType);
  331.             if ($baseQuantity >= $Lot) { return; }
  332.   
  333.             // 刺繍 (Kind = 3) の場合は常に $PrintLot = 2000
  334.             if ($Kind == 3) {
  335.                 $PrintLot 2000;
  336.             } else {
  337.                 // その他の場合は既存のロジックを維持
  338.                 $PrintLot $this->CommonService->GetConfig('PRINT_LOT');
  339.                 if ($isKyouei) { 
  340.                     $PrintLot 500;
  341.                 }
  342.             }
  343.             
  344.             $this->Characters['lot']['title'] = $this->EstimateText['Text06'];
  345.             $this->Characters['lot']['total'] = $PrintLot;
  346.             $this->LotKindAlreadyShown[$lotKey] = true;
  347.             return;
  348.         }
  349.         $baseQuantity $this->LotBaseQuantity $this->LotBaseQuantity $this->Quantity;
  350.         if ($baseQuantity $Lot) {
  351.             // この場合はKindが指定されていないため、既存のロジックを維持
  352.             // (Kind = 3の判定は上記の$Kind != nullの場合で処理される)
  353.             if ($Kind == 3) {
  354.                 $PrintLot 2000;
  355.             } else {
  356.                 $PrintLot $this->CommonService->GetConfig('PRINT_LOT');
  357.                 if ($isKyouei) { 
  358.                     $PrintLot 500;
  359.                 }
  360.             }
  361.             $this->Characters['lot']['title'] = $this->EstimateText['Text06'];
  362.             $this->Characters['lot']['total'] = $PrintLot;
  363.         }
  364.     }
  365.     /**
  366.      * Create lot key based on Kind and PrintType logic
  367.      */
  368.     protected function createLotKey($Kind$PrintType null)
  369.     {
  370.         switch ($Kind) {
  371.             case 1// シルクプリント
  372.             case 4// マジック
  373.             case 3// 刺繍
  374.                 return $Kind '_' . ($PrintType ?? 'default');
  375.             case 2// デジタル転写
  376.             default:
  377.                 return (string)$Kind;
  378.         }
  379.     }
  380.     /**
  381.      * Get base quantity by Kind and PrintType combination
  382.      */
  383.     protected function getBaseQuantityByKindAndType($Kind$PrintType null)
  384.     {
  385.         // Create key based on the same logic as createLotKey
  386.         $key $this->createLotKey($Kind$PrintType);
  387.         
  388.         // If we have specific Kind+PrintType data, use it
  389.         if (isset($this->LotBaseQuantityByKindAndType[$key])) {
  390.             return (int)$this->LotBaseQuantityByKindAndType[$key];
  391.         }
  392.         
  393.         // Fallback to original logic for backward compatibility
  394.         switch ($Kind) {
  395.             case 1: return $this->LotQtyPrint;
  396.             case 4: return $this->LotQtyMagic;
  397.             case 3: return $this->LotQtyShishu;
  398.             case 2: return $this->LotQtyTensha;
  399.             default: return (int)($this->LotBaseQuantityByKind[$Kind] ?? 0);
  400.         }
  401.     }
  402.     protected function SetPrintTotal($Total)
  403.     {
  404.         if (isset($this->Characters['print_total'])) {
  405.             $this->Characters['print_total']['total'] += $Total;
  406.         } else {
  407.             $this->Characters['print_total']['title'] = $this->EstimateText['Text09'];
  408.             $this->Characters['print_total']['total'] = $Total;
  409.         }
  410.     }
  411.     protected function SetTotal($Total)
  412.     {
  413.         if (isset($this->Characters['total'])) {
  414.             $this->Characters['total'] += $Total;
  415.         } else {
  416.             $this->Characters['total'] = $Total;
  417.         }
  418.     }
  419.     protected function SetItems($Name$Title$Price 0$Quantity 0$Code null$Genka null$Detail = [], $basePrice 0$baseGenke null$opdKataType null)
  420.     {
  421.         if (!$Quantity) {
  422.              $Quantity $this->Quantity;
  423.         }
  424.         if ($opdKataType !== null){
  425.             $Re[$Name]['base_tanka'] = (int)$basePrice;
  426.             $Re[$Name]['base_genka'] = (int)$baseGenke;
  427.             $Re[$Name]['opdKataType'] = $opdKataType;
  428.         }
  429.         $Text number_format($Price) . $this->EstimateText['Text02'] . $Quantity $this->EstimateText['Text04'];
  430.         if ('print' == $Name) {
  431.             $Title $this->EstimateText['Text19'] . $Text;
  432.         }
  433.         if ('shishu' == $Name) {
  434.             $Title .= $Text;
  435.         }
  436.         $Re[$Name]['title'] = $Title;
  437.       //  if ($Price) {
  438.             $Total $Price $Quantity;
  439.             $Re[$Name]['total'] = $Total;
  440.             $this->SetPrintTotal($Total);
  441.             $this->SetTotal($Total);
  442.        // }
  443.         //
  444.         $Re[$Name]['code'] = $Code;
  445.         $Re[$Name]['quantity'] = (int)$Quantity;
  446.         $Re[$Name]['tanka'] = (int)$Price;
  447.         $Re[$Name]['genka'] = (int)$Genka;
  448.         //
  449.         if (!empty($Code) && ($EstimateDetail $this->EstimateDetailList[$Code])) {
  450.             if (empty($Detail['name'])) {
  451.                 $Detail['name'] = $EstimateDetail['name'];
  452.             }
  453.             if (empty($Detail['amount1']) && (!empty($Quantity))) {
  454.                 $Detail['amount1'] = $Quantity;
  455.             }
  456.             if (empty($Detail['tanni1']) && (!empty($Detail['amount1'])) && !empty($EstimateDetail['tanni1'])) {
  457.                 $Detail['tanni1'] = $EstimateDetail['tanni1'];
  458.             }
  459.             if (empty($Detail['tanni2']) && (!empty($Detail['amount2'])) && !empty($EstimateDetail['tanni2'])) {
  460.                 $Detail['tanni2'] = $EstimateDetail['tanni2'];
  461.             }
  462.             if (empty($Detail['amount'])) {
  463.                 $Detail['amount'] = $Detail['amount2'] ?? $Detail['amount1'];
  464.             }
  465.         }
  466.         //
  467.         $Re[$Name]['detail'] = $Detail;
  468.         //
  469.         $this->SetCharacters('item'$Re);
  470.     }
  471.     /** 本番では不要 */
  472.     public function SetOptions($Options, &$OptionTotal)
  473.     {
  474.         foreach (self::OptionTotalColumn as $Column) {
  475.             $Re = [];
  476.             if ($Total $OptionTotal[$Column] ?? null) {
  477.                 $Options[$Column] = $Total;
  478.             } else {
  479.                 $Re[$Column] = $Options[$Column] ?? null;
  480.             }
  481.         }
  482.         if (count($Re) > 0) {
  483.             $OptionTotal $Re;
  484.         }
  485.         return $Options;
  486.     }
  487.     protected function SetTitle($Kind$Position$Katamei null$Color null)
  488.     {
  489.         $this->SetCharacters('Type'$Kind);
  490.         $Name $this->PrintName[$Kind];
  491.         $this->SetCharacters('Type_Name'$Name);
  492.         if ($Katamei) {
  493.             $this->SetCharacters('katamei'$Katamei);
  494.         }
  495.         if ($Color) {
  496.             $this->SetCharacters('color'$Color);
  497.         }
  498.         $Title "{$this->EstimateLine}ヶ所目 ({$Position}{$Name}";
  499.         if ($this->Page) {
  500.             $Title $Title ' ' $Katamei;
  501.         }
  502.         $this->SetItems('title'$Title);
  503.         $this->SetCharacters('Position'$Position);
  504.     }
  505.     protected function SetCharacters($Name$Value)
  506.     {
  507.         $Name strtolower($Name);
  508.         if ($this->EstimateLine 0) {
  509.             $Data $this->Characters[$this->EstimateType][$this->EstimateLine][$Name] ?? null;
  510.             switch (gettype($Data)) {
  511.                 case 'array':
  512.                     $this->Characters[$this->EstimateType][$this->EstimateLine][$Name] = array_merge($Data$Value);
  513.                     break;
  514.                 default:
  515.                     $this->Characters[$this->EstimateType][$this->EstimateLine][$Name] = $Value;
  516.             }
  517.             return;
  518.         }
  519.         $this->Characters[$this->EstimateType][$Name] = $Value;
  520.         return;
  521.     }
  522.     protected function GetPrint($Options)
  523.     {
  524.         if (!$Prints $Options['print'] ?? null) {
  525.             return;
  526.         };
  527.         $Num $Options['kakou_me'] ?? 0;
  528.         $this->RepeatDate $Options['date'] ?? null;
  529.           // Check if product is KYOEI supplier
  530.           $isKyouei false;
  531.           if (isset($Options['product_class_id'])) {
  532.               $isKyouei $this->hasKyouei($Options['product_class_id']);
  533.           }
  534.         $this->SetEstimateType(1);
  535.         for ($i 1$i <= $Num$i++) {
  536.             $this->EstimateLine $i;
  537.             $Print $Prints[$i];
  538.             if (!$Kind $Print['position'] ?? null) {
  539.                 return 'P1';
  540.             }
  541.             $this->Quantity $Options['quantity'] ?? 0;
  542.             switch ($Kind) {
  543.                 case #シルクプリント
  544.                     $Type $Print['print_type'] ?? null;
  545.                     $Iti $Print['print_position'] ?? null;
  546.                     $Color $Print['print_detail'] ?? null;
  547.                     if ($this->CommonService->IsNull([$Type$Color])) {
  548.                         continue 2;
  549.                     }
  550.                     if (!$Types $this->EstimateConverter->getPrintDetail($Type)) {
  551.                         return 'P1';
  552.                     }
  553.                     if (!$Itis $this->EstimateConverter->getKakuninIchiById($Iti)) {
  554.                         // return 'P2';
  555.                         $Itis self::ICHI_OTHER;
  556.                     }
  557.                     #タイトル
  558.                     $Position $this->SetItiName($Itis['ichi_name']);
  559.                     $Katamei $Types['pd_katamei'];
  560.                     try {
  561.                         $this->SetTitle($Kind$Position$Katamei$Color);
  562.                     } catch (\Exception $e) {
  563.                         log_error($e->getMessage());
  564.                         throw new NotFoundHttpException($e->getMessage());
  565.                     }
  566.                     #型代
  567.                     $Kata =$this->SetKatadai(1,$Types['pd_katadai_tax10']);
  568.                     $baseKata $Types['pd_katadai_tax10'];
  569.                     $Code '993'// TODO: Fetch actually data.
  570.                     $opd_kata_type $this->SetKatadaiType();
  571.                     $Genka $this->SetKatadai(1,$Types['pd_katadai_genka'],$Types['pd_katadai_saihan']);
  572.                     $baseGenka $opd_kata_type $Genka $Types['pd_katadai_genka'];
  573.                     $Kako $this->EstimateText['Text01'];
  574.                     $Title $Kako[$Types['print_kakou']] . number_format($Kata) . $this->EstimateText['Text02'] . $Color $this->EstimateText['Text16'];
  575.                     $this->SetItems('kata'$Title$Kata$Color$Code$Genka, [], $baseKata$baseGenka $opd_kata_type);
  576.                     #プリント代
  577.                     $Price $Types["pd_{$Color}print_tax10"];
  578.                     $Quantity null;
  579.                     $Code '996'// TODO: Fetch actually data.
  580.                     $Genka $Types["pd_{$Color}print_genka"];
  581.                     $this->SetItems('print'''$Price$Quantity$Code$Genka);
  582.                     // Only set lots if product is from KYOEI supplier
  583.                     if ($isKyouei) {
  584.                         $this->SetLots($Types['print_lot'], $Kind$Type$isKyouei);
  585.                     }
  586.                     break;
  587.                 case #マジック
  588.                     $Type $Print['magic_type'] ?? null;
  589.                     $Iti $Print['magic_position'] ?? null;
  590.                     if ($this->CommonService->IsNull([$Type])) {
  591.                         continue 2;
  592.                     }
  593.                     if (!$Types $this->EstimateConverter->getMagickPrintDetail($Type)) {
  594.                         return 'P3';
  595.                     }
  596.                     if (!$Itis $this->EstimateConverter->getKakuninIchiById($Iti)) {
  597.                         // return 'P4';
  598.                         $Itis self::ICHI_OTHER;
  599.                     }
  600.                     #️タイトル
  601.                     $Position $this->SetItiName($Itis['ichi_name']);
  602.                     $Katamei $Types['md_katamei'];
  603.                     $this->SetTitle($Kind$Position$Katamei);
  604.                     #プリント代
  605.                     $Quantity null;
  606.                     $Code '996'// TODO: Fetch actually data.
  607.                     $Genka $Types["md_print_genka"];
  608.                     $this->SetItems('print'''$Types["md_print_tax10"], $Quantity$Code$Genka);
  609.                     
  610.                     // Only set lots if product is from KYOEI supplier
  611.                     if ($isKyouei) {
  612.                         $this->SetLots($Types['magic_lot'], $Kind$Type$isKyouei);
  613.                     }
  614.                     break;
  615.                 case #刺繍
  616.                     $Type $Print['shishu_type'] ?? null;
  617.                     $Iti $Print['shishu_position'] ?? null;
  618.                     $Color $Print['shishu_detail'] ?? null;
  619.                     if ($this->CommonService->IsNull([$Type$Color])) {
  620.                         continue 2;
  621.                     }
  622.                     if (!$Types $this->EstimateConverter->getShishuDetail($Type)) {
  623.                         return 'P5';
  624.                     }
  625.                     if (!$Itis $this->EstimateConverter->getKakuninIchiById($Iti)) {
  626.                         // return 'P6';
  627.                         $Itis self::ICHI_OTHER;
  628.                     }
  629.                     #️タイトル
  630.                     $Position $this->SetItiName($Itis['ichi_name']);
  631.                     $Katamei $Types['sd_name'];
  632.                     $this->SetTitle($Kind$Position$Katamei$Color);
  633.                     #パンチ
  634.                     $Title $this->EstimateText['Text07'];
  635.                     $Quantity 1;
  636.                     $Code '907'// TODO: Fetch actually data.
  637.                     $Detail = [
  638.                         'amount' => 1,
  639.                     ];
  640.                     $panchi $this->SetKatadai(3,$Types['sd_panchi_tax10']);
  641.                     $Genka = ((int)$panchi === 0) && !($Print['is_kamino'] ?? false) ? $Types["sd_panchi_genka"];
  642.                     $this->SetItems('panchi'$Title$panchi$Quantity$Code$Genka$Detail);
  643.                     #刺繍代
  644.                     $Title $this->PrintName[3] . $this->EstimateText['Text03'] . ' ' $Color $this->EstimateText['Text16'] . ' ';
  645.                     $Quantity null;
  646.                     $Code '904'// TODO: Fetch actually data.
  647.                     $Genka $Types["sd_{$Color}shishu_genka"];
  648.                     $this->SetItems('shishu'$Title$Types["sd_{$Color}shishu_tax10"], $Quantity$Code$Genka);
  649.               
  650.                     
  651.                     // 刺繍 (Kind = 3) の場合は常に SetLots を呼び出す(supplierに関係なく)
  652.                     // For other kinds, only set lots if product is from KYOEI supplier
  653.                     if ($Kind == || $isKyouei) {
  654.                         $this->SetLots($Types['shishu_lot'], $Kind$Type$isKyouei);
  655.                     }
  656.                     break;
  657.                 case #デジタル転写 特殊
  658.                     $Type $Option['tensya'] ?? 1;
  659.                     $Iti $Print['tensha_position'] ?? null;
  660.                     $Tate $Print['tensha_tate'] ?? null;
  661.                     $Yoko $Print['tensha_yoko'] ?? null;
  662.                     if ($this->CommonService->IsNull([$Tate$Yoko])) {
  663.                         continue 2;
  664.                     }
  665.                     if (!$Tensyas $this->EstimateConverter->getTenshaDetailListById($Type)) {
  666.                         return 'P5';
  667.                     }
  668.                     if (!$Itis $this->EstimateConverter->getKakuninIchiById($Iti)) {
  669.                         // return 'P6';
  670.                         $Itis self::ICHI_OTHER;
  671.                     }
  672.                     $Types $Tensyas[0];
  673.                     #️タイトル
  674.                     $Position $this->SetItiName($Itis['ichi_name']);
  675.                     $Katamei $Tate $this->EstimateText['Text17'] . $this->EstimateText['Text18'] . $Yoko $this->EstimateText['Text17'];
  676.                     $this->SetTitle($Kind$Position$Katamei);
  677.                     #型代
  678.                     $Kata 1;
  679.                     if (is_null($Types['tensha_kata_kotei'])) {
  680.                         $Kata $Options['white_only'];
  681.                     }
  682.                     $Price $this->SetKatadai(2,$Types['tensha_katadai_tax10']);
  683.                     $opd_kata_type $this->SetKatadaiType();
  684.                     $basePrice $Types['tensha_katadai_tax10'];
  685.                     $Title $this->EstimateText['Text11'] . number_format($Price) . $this->EstimateText['Text02'] . $Kata $this->EstimateText['Text12'];
  686.                     $Code '993'// TODO: Fetch actually data.
  687.                     $Genka $this->SetKatadai(2,$Types['tensha_katadai_genka'],$Types['tensha_katadai_saihan']);
  688.                     $baseGenka $opd_kata_type $Genka $Types['tensha_katadai_genka'];
  689.                     $this->SetItems('kata'$Title$Price$Kata$Code$Genka, [], $basePrice$baseGenka$opd_kata_type);
  690.                     #シート 未定
  691.                     $Price $Types['tensha_sheet_tax10'];
  692.                     $Sheet $this->GetSheet($Types$Tate$Yoko);
  693.                     $Title $this->EstimateText['Text13'] . number_format($Price) . $this->EstimateText['Text02'] . $Sheet $this->EstimateText['Text14'];
  694.                     $Code '995'// TODO: Fetch actually data.
  695.                     $Genka $Types["tensha_sheet_genka"];
  696.                     $this->SetItems('sheet'$Title$Price$Sheet$Code$Genka);
  697.                     #圧着代:
  698.                     $Price $Types['tensha_acchaku_tax10'];
  699.                     $Quantity null;
  700.                     $Code '997'// TODO: Fetch actually data.
  701.                     $Genka $Types["tensha_acchaku_genka"];
  702.                     $Title $this->EstimateText['Text15'] . number_format($Price) . $this->EstimateText['Text02'] . $this->Quantity $this->EstimateText['Text04'];
  703.                     $Detail= [
  704.                         'amount1' => 1,
  705.                         'amount2' => $this->Quantity,
  706.                     ];
  707.                     $this->SetItems('acchaku'$Title$Price$Quantity$Code$Genka$Detail);
  708.                     // Only set lots if product is from KYOEI supplier
  709.                     if ($isKyouei) {
  710.                         $this->SetLots($Types['tensha_lot'], $Kind$Type$isKyouei);
  711.                     }
  712.                     break;
  713.             }
  714.         }
  715.     }
  716.     public function GetCharacters()
  717.     {
  718.         return $this->Characters;
  719.     }
  720.     protected function GetSusoage($Options)
  721.     {
  722.         if (!$Susoages $Options['susoage'] ?? null) {
  723.             return;
  724.         }
  725.         $this->SetEstimateType(2);
  726.         $Num 0;
  727.         foreach ($Susoages as $ProductId => $Susoage) {
  728.             foreach ($Susoage as $Data) {
  729.                 if (!$Data['do']) {
  730.                     continue;
  731.                 }
  732.                 $Num++;
  733.             }
  734.         }
  735.         $Susoage $this->EstimateConverter->GetSusoage();
  736.         $Price $Susoage['susoage_print_tax10'];
  737.         $Value =
  738.         $Total $Price $Num;
  739.         $this->SetCharacters('name'$this->EstimateText['Text21']);
  740.         $this->SetCharacters('hemming'$this->EstimateText['Text22']);
  741.         $this->SetCharacters('title'$this->EstimateText['Text22'] . ' ' $Price $this->EstimateText['Text02'] . $Num $this->EstimateText['Text04']);
  742.         $this->SetCharacters('price'$Price);
  743.         $this->SetCharacters('suu'$Num);
  744.         $this->SetCharacters('total'$Total);
  745.         $this->Characters['total'] = $Total;
  746.     }
  747.     /**
  748.      * 2022/08/31
  749.      * 大口割聞を一括管理する
  750.      *
  751.      * 商品の合計と、加工賃で大口割引とする
  752.      * @param int $ProductTotal 商品合計
  753.      * @param int $ProcessingCost 加工費
  754.      *
  755.      */
  756.     public function SetLargeDiscount($ProductTotal$ProcessingCost 0)
  757.     {
  758.         if($this->CartService->GetCartType()>=CartService::CartTypeSample){return;}
  759.         $Total $ProductTotal $ProcessingCost;
  760.         $Total_ = ($orderGroup Order::getOrderGroup()) ? $orderGroup->getSubTotal() : $Total;
  761.         foreach ($this->CommonService->GetConfig('LargeDiscount') as $Param) {
  762.             if ($Total_ >= $Param['more']) {
  763.                 $this->Ritu $Param['discount'];
  764.             }
  765.         }
  766.         if (!$this->Ritu) {
  767.             return 0;
  768.         }
  769.         #2022/11/25 四捨五入から切り上げへ
  770.         //$TaxRule = $this->TaxRuleService->getTaxRule();
  771.         #$RoundingType = $TaxRule->getRoundingType();
  772.         $Discount $Total $this->Ritu 100;
  773.         $this->LargeDiscount $this->TaxRuleService->roundByRoundingType($Discount\Eccube\Entity\Master\RoundingType::CEIL);
  774.         return $this->Ritu;
  775.     }
  776.     public function GetLargeDiscount()
  777.     {
  778.         return $this->LargeDiscount;
  779.     }
  780.     /**
  781.      * キャンペーン割引の合計を取得
  782.      */
  783.     public function GetCampaignDiscount()
  784.     {
  785.         return (int)$this->CampaignDiscount;
  786.     }
  787.     /**
  788.      * キャンペーン割引の内訳を取得
  789.      */
  790.     public function GetCampaignDetails()
  791.     {
  792.         return $this->CampaignDetails;
  793.     }
  794.     /**
  795.      * カートアイテムからキャンペーン割引を算出
  796.      * ・よりどりみどり割: campaign_yoridori=1 のアイテム合計。数量閾値以上で%割引
  797.      * ・まとめ割: campaign_matome=1 を商品毎に集計。数量閾値以上の商品のみ%割引
  798.      */
  799.     protected function computeCampaignDiscount($Items)
  800.     {
  801.         $this->CampaignDiscount 0;
  802.         $this->CampaignDetails = [];
  803.         if (empty($Items)) {
  804.             return;
  805.         }
  806.         // 期間内の有効キャンペーンを取得
  807.         $now = (new \DateTime())->format('Y-m-d H:i:s');
  808.         $campaigns = (new \Lm\Service\Db\SqlService())
  809.             ->Table('campaign_table')
  810.             ->Where('campaign_ddate IS NULL')
  811.             ->Where('campaign_start <= :now')
  812.             ->Where('campaign_end >= :now')
  813.             ->Where('goods_canonical_hinban IS NOT NULL')
  814.             ->Param(':now'$now)
  815.             ->FindAll();
  816.         if (empty($campaigns) || empty($Items)) { 
  817.             return; 
  818.         }
  819.         // 単価(税込)を取得するヘルパー
  820.         $getUnitPrice = function($Item) {
  821.             try {
  822.                 // 優先: 直接の税込単価があればそれを使う
  823.                 if (method_exists($Item'getPriceIncTax')) {
  824.                     return (int)$Item->getPriceIncTax();
  825.                 }
  826.                 // 次善: 税抜単価に対して税込化
  827.                 if (method_exists($Item'getPrice')) {
  828.                     $ex = (int)$Item->getPrice();
  829.                     // TaxRuleService 経由で税込化 (四捨五入ルールに追従)
  830.                     if ($this->TaxRuleService) {
  831.                         return (int)$this->TaxRuleService->getIncTax($ex);
  832.                     }
  833.                     // フォールバック: 現行税率で簡易税込化
  834.                     $taxRate method_exists($this->TaxRuleService'getTaxRule') ? $this->TaxRuleService->getTaxRule()->getTaxRate() : 10;
  835.                     return (int) round($ex * (+ ($taxRate 100)));
  836.                 }
  837.             } catch (\Throwable $e) {}
  838.             return 0;
  839.         };
  840.         // 集計: 各商品ごとの小計/数量/フラグ
  841.         // goodsId => ['qty'=>int,'sum'=>int,'yoridori'=>0/1,'matome'=>0/1, 'hinban' => string, 'productCodes' => array]
  842.         $goodsMap = [];
  843.         // productCode => goodsId mapping to count different product types
  844.         $productCodeToGoodsId = []; // productCode => goodsId
  845.         foreach ($Items as $Item) {
  846.             if (!method_exists($Item'getProductClass') || !method_exists($Item'getQuantity')) { 
  847.                 continue; 
  848.             }
  849.             
  850.             $productClass $Item->getProductClass();
  851.             if (!$productClass) {
  852.                 continue;
  853.             }
  854.             
  855.             $product $productClass->getProduct();
  856.             if (!$product) { 
  857.                 continue; 
  858.             }
  859.             $goodsId $product->getId();
  860.             $qty = (int)$Item->getQuantity();
  861.             $unit $getUnitPrice($Item);
  862.             $productCode $productClass->getCode() ?? '';
  863.             // フラグは EC 側の Goods エンティティに合わせて取得(なければスキップ)
  864.             $yoridori 0$matome 0;
  865.             $goodsHinban '';
  866.             try {
  867.                 // Query campaign flags directly from goods_table
  868.                 $sqlService = new \Lm\Service\Db\SqlService();
  869.                 $goodsData $sqlService
  870.                     ->Table('goods_table')
  871.                     ->Where('goods_id = :goods_id')
  872.                     ->Param(':goods_id'$goodsId)
  873.                     ->Find();
  874.                 
  875.                 if ($goodsData) {
  876.                     $yoridori = (int)($goodsData['campaign_yoridori'] ?? 0);
  877.                     $matome = (int)($goodsData['campaign_matome'] ?? 0);
  878.                     $goodsHinban trim($goodsData['goods_canonical_hinban'] ?? '');
  879.                 }
  880.             } catch (\Throwable $e) {
  881.                 // Skip error
  882.             }
  883.             if (!isset($goodsMap[$goodsId])) {
  884.                 $goodsMap[$goodsId] = ['qty' => 0'sum' => 0'yoridori' => 0'matome' => 0'hinban' => '''productCodes' => []];
  885.             }
  886.             $goodsMap[$goodsId]['qty'] += $qty;
  887.             $goodsMap[$goodsId]['sum'] += ($unit $qty);
  888.             if ($yoridori === 1) { $goodsMap[$goodsId]['yoridori'] = 1; }
  889.             if ($matome === 1) { $goodsMap[$goodsId]['matome'] = 1; }
  890.             if (!empty($goodsHinban)) { $goodsMap[$goodsId]['hinban'] = $goodsHinban; }
  891.             if (!empty($productCode)) {
  892.                 $goodsMap[$goodsId]['productCodes'][$productCode] = true;
  893.                 $productCodeToGoodsId[$productCode] = $goodsId;
  894.             }
  895.         }
  896.         
  897.         // キャンペーンを種別でグルーピング: 1=よりどり → 2=まとめ
  898.         $yoridoriCampaigns = [];
  899.         $matomeCampaigns = [];
  900.         foreach ($campaigns as $c) {
  901.             $type = (int)($c['campaign_type'] ?? 0);
  902.             if ($type !== && $type !== 2) {
  903.                 continue;
  904.             }
  905.             $flagKey $type === 'yoridori' 'matome';
  906.             $campaignHinbans $c['goods_canonical_hinban'] ?? '';
  907.             $hinbanList null;
  908.             if (!empty($campaignHinbans)) {
  909.                 $hinbanList array_filter(array_map('trim'explode(','$campaignHinbans)), function ($value) {
  910.                     return $value !== '';
  911.                 });
  912.                 if (empty($hinbanList)) {
  913.                     $hinbanList null;
  914.                 }
  915.             }
  916.             $targetsGoods = [];
  917.             $totalQty 0;
  918.             $totalSum 0;
  919.             foreach ($goodsMap as $gid => $g) {
  920.                 if (($g[$flagKey] ?? 0) !== 1) {
  921.                     continue;
  922.                 }
  923.                 if ($hinbanList !== null) {
  924.                     $goodsHinban $g['hinban'] ?? '';
  925.                     if ($goodsHinban === '' || !in_array($goodsHinban$hinbanListtrue)) {
  926.                         continue;
  927.                     }
  928.                 }
  929.                 $targetsGoods[$gid] = $g;
  930.                 $totalQty += (int)($g['qty'] ?? 0);
  931.                 $totalSum += (int)($g['sum'] ?? 0);
  932.             }
  933.             if (empty($targetsGoods)) {
  934.                 continue;
  935.             }
  936.             $entry = [
  937.                 'campaign' => $c,
  938.                 'targets' => [
  939.                     'goods' => $targetsGoods,
  940.                     'qty' => $totalQty,
  941.                     'sum' => $totalSum,
  942.                 ],
  943.             ];
  944.             if ($type === 1) {
  945.                 $yoridoriCampaigns[] = $entry;
  946.             } else {
  947.                 $matomeCampaigns[] = $entry;
  948.             }
  949.         }
  950.         // カート内の異なる商品種類数(商品ID単位)
  951.         $totalUniqueProductCount 0;
  952.         foreach ($goodsMap as $gid => $g) {
  953.             if ((int)($g['qty'] ?? 0) > 0) {
  954.                 $totalUniqueProductCount++;
  955.             }
  956.         }
  957.         // よりどり: 同一キャンペーンIDごとにマージして合計 qty/sum で判定・割引
  958.         $mergedYoridori = [];
  959.         foreach ($yoridoriCampaigns as $entry) {
  960.             // よりどりはカート内に2種類以上の商品がある場合のみ適用
  961.             if ($totalUniqueProductCount 2) {
  962.                 continue;
  963.             }
  964.             $c $entry['campaign'];
  965.             $cid = (int)($c['campaign_id'] ?? 0);
  966.             $minQty = (int)($c['min_product_count'] ?? 0);
  967.             $percent = (float)($c['discount_percent'] ?? 0);
  968.             $cname $c['campaign_name'] ?? '';
  969.             $key $cid ? (string)$cid : ($cname '|' $minQty '|' $percent);
  970.             if (!isset($mergedYoridori[$key])) {
  971.                 $mergedYoridori[$key] = [
  972.                     'campaign' => $c,
  973.                     'targets' => [
  974.                         'goods' => [],
  975.                         'qty' => 0,
  976.                         'sum' => 0,
  977.                     ],
  978.                 ];
  979.             }
  980.             // merge targets: sum qty/sum; keep union goods (last wins)
  981.             $mergedYoridori[$key]['targets']['qty'] += (int)($entry['targets']['qty'] ?? 0);
  982.             $mergedYoridori[$key]['targets']['sum'] += (int)($entry['targets']['sum'] ?? 0);
  983.             foreach (($entry['targets']['goods'] ?? []) as $gid => $g) {
  984.                 $mergedYoridori[$key]['targets']['goods'][$gid] = $g;
  985.             }
  986.         }
  987.         // replace list
  988.         $yoridoriCampaigns array_values($mergedYoridori);
  989.         // まとめ: 同一キャンペーンIDごとにマージして合算(よりどりと同様の処理)
  990.         $mergedMatome = [];
  991.         foreach ($matomeCampaigns as $entry) {
  992.             $c $entry['campaign'];
  993.             $cid = (int)($c['campaign_id'] ?? 0);
  994.             $minQty = (int)($c['min_product_count'] ?? 0);
  995.             $percent = (float)($c['discount_percent'] ?? 0);
  996.             $cname $c['campaign_name'] ?? '';
  997.             $key $cid ? (string)$cid : ($cname '|' $minQty '|' $percent);
  998.             if (!isset($mergedMatome[$key])) {
  999.                 $mergedMatome[$key] = [
  1000.                     'campaign' => $c,
  1001.                     'targets' => [
  1002.                         'goods' => [],
  1003.                         'qty' => 0,
  1004.                         'sum' => 0,
  1005.                     ],
  1006.                 ];
  1007.             }
  1008.             // merge targets: sum qty/sum; keep union goods (last wins)
  1009.             $mergedMatome[$key]['targets']['qty'] += (int)($entry['targets']['qty'] ?? 0);
  1010.             $mergedMatome[$key]['targets']['sum'] += (int)($entry['targets']['sum'] ?? 0);
  1011.             foreach (($entry['targets']['goods'] ?? []) as $gid => $g) {
  1012.                 $mergedMatome[$key]['targets']['goods'][$gid] = $g;
  1013.             }
  1014.         }
  1015.         // replace list
  1016.         $matomeCampaigns array_values($mergedMatome);
  1017.         // よりどり優先仕様:
  1018.         // 1) 先によりどりを判定・適用
  1019.         // 2) よりどり適用済み商品は、まとめの対象から除外
  1020.         $yoridoriAppliedGoodsIds = [];
  1021.         foreach ($yoridoriCampaigns as $entry) {
  1022.             if ($totalUniqueProductCount 2) {
  1023.                 continue;
  1024.             }
  1025.             $c $entry['campaign'];
  1026.             $targets $entry['targets'];
  1027.             $minQty = (int)($c['min_product_count'] ?? 0);
  1028.             $percent = (float)($c['discount_percent'] ?? 0);
  1029.             $cid = (int)($c['campaign_id'] ?? 0);
  1030.             $cname $c['campaign_name'] ?? '';
  1031.             if ($percent <= 0) {
  1032.                 continue;
  1033.             }
  1034.             $eligibleGoods $targets['goods'] ?? [];
  1035.             $eligibleQty = (int)($targets['qty'] ?? 0);
  1036.             $eligibleSum = (int)($targets['sum'] ?? 0);
  1037.             // よりどりは同一キャンペーン内で2種類以上の商品が必要
  1038.             if (count($eligibleGoods) < 2) {
  1039.                 continue;
  1040.             }
  1041.             if ($eligibleQty $minQty || $eligibleSum <= 0) {
  1042.                 continue;
  1043.             }
  1044.             $discount = (int)round($eligibleSum $percent 100);
  1045.             if ($discount <= 0) {
  1046.                 continue;
  1047.             }
  1048.             $this->CampaignDiscount += $discount;
  1049.             $this->CampaignDetails[] = [
  1050.                 'id' => $cid,
  1051.                 'type' => 1,
  1052.                 'name' => $cname,
  1053.                 'amount' => $discount,
  1054.                 'min' => $minQty,
  1055.                 'percent' => $percent,
  1056.             ];
  1057.             foreach ($eligibleGoods as $gid => $_g) {
  1058.                 $yoridoriAppliedGoodsIds[$gid] = true;
  1059.             }
  1060.         }
  1061.         // 2) まとめ: よりどり適用済み商品を除外して判定・適用
  1062.         foreach ($matomeCampaigns as $entry) {
  1063.             $c $entry['campaign'];
  1064.             $targets $entry['targets'];
  1065.             $minQty = (int)($c['min_product_count'] ?? 0);
  1066.             $percent = (float)($c['discount_percent'] ?? 0);
  1067.             $cid = (int)($c['campaign_id'] ?? 0);
  1068.             $cname $c['campaign_name'] ?? '';
  1069.             if ($percent <= 0) {
  1070.                 continue;
  1071.             }
  1072.             $campaignDiscountTotal 0;
  1073.             foreach (($targets['goods'] ?? []) as $gid => $g) {
  1074.                 if (isset($yoridoriAppliedGoodsIds[$gid]) && $yoridoriAppliedGoodsIds[$gid]) {
  1075.                     continue;
  1076.                 }
  1077.                 $goodsQty = (int)($g['qty'] ?? 0);
  1078.                 if ($goodsQty $minQty) {
  1079.                     continue;
  1080.                 }
  1081.                 $base = (int)($g['sum'] ?? 0);
  1082.                 if ($base <= 0) {
  1083.                     continue;
  1084.                 }
  1085.                 $goodsDiscount = (int)round($base $percent 100);
  1086.                 if ($goodsDiscount <= 0) {
  1087.                     continue;
  1088.                 }
  1089.                 $campaignDiscountTotal += $goodsDiscount;
  1090.             }
  1091.             if ($campaignDiscountTotal 0) {
  1092.                 $this->CampaignDiscount += $campaignDiscountTotal;
  1093.                 $this->CampaignDetails[] = [
  1094.                     'id' => $cid,
  1095.                     'type' => 2,
  1096.                     'name' => $cname,
  1097.                     'amount' => $campaignDiscountTotal,
  1098.                     'min' => $minQty,
  1099.                     'percent' => $percent,
  1100.                 ];
  1101.             }
  1102.         }
  1103.         
  1104.     }
  1105.     /**
  1106.      * デジタル転写シートを算出
  1107.      *  $Tate = 230;$Yoko = 60; 2パターンの方が多くなる
  1108.      *
  1109.      */
  1110.     protected function GetSheet($Data$Tate$Yoko)
  1111.     {
  1112.         //$Tate = 190;$Yoko = 130;
  1113.         $MaxTate $Data['tensha_tate'];
  1114.         $MaxYoko $Data['tensha_yoko'];
  1115.         $Tate += (int)$this->CommonService->GetConfig('ESTIMATE_YOHAKU');
  1116.         $Yoko += (int)$this->CommonService->GetConfig('ESTIMATE_YOHAKU');
  1117.         #1 縦横で算出
  1118.         $S1 floor($MaxTate $Tate) * floor($MaxYoko $Yoko);
  1119.         #2 横縦で算出
  1120.         $S2 floor($MaxTate $Yoko) * floor($MaxYoko $Tate);
  1121.         if(max($S1$S2) == 0){
  1122.             return $this->Quantity;
  1123.         }
  1124.         $Sheet $this->Quantity max($S1$S2);
  1125.         return ceil($Sheet);
  1126.     }
  1127.     /**
  1128.      * @param object $Items
  1129.      * @param $Param
  1130.      *
  1131.      */
  1132.     public function SetEstimateOption($Items$Param$Page 'cart')
  1133.     {
  1134.         // カートアイテムを保持(キャンペーン計算用)
  1135.         if (is_array($Items)) {
  1136.             $this->LastCartItems $Items;
  1137.         } else if (is_iterable($Items)) {
  1138.             $this->LastCartItems iterator_to_array($Items);
  1139.         }
  1140.         
  1141.         // キャンペーン割引を計算(SetEstimateOptionで直接実行)
  1142.         $this->computeCampaignDiscount($this->LastCartItems);
  1143.         $Options = [];
  1144.         $Total 0;
  1145.         // ロット合算対象(printあり)のみ数量集計
  1146.         $this->LotBaseQuantity 0;
  1147.         $this->LotBaseQuantityByKind = [];
  1148.         $this->LotBaseQuantityByKindAndType = [];
  1149.         $this->LotQtyPrint 0;
  1150.         $this->LotQtyMagic 0;
  1151.         $this->LotQtyShishu 0;
  1152.         $this->LotQtyTensha 0;
  1153.         $this->LotKindAlreadyShown = [];
  1154.         foreach ($Items as $qItem) {
  1155.             $qty method_exists($qItem'getQuantity') ? (int)$qItem->getQuantity() : 0;
  1156.             if ($qty <= 0) { continue; }
  1157.             $opts null;
  1158.             if (method_exists($qItem'getOptionsData')) {
  1159.                 $opts $qItem->getOptionsData();
  1160.             } elseif (method_exists($qItem'getOptions')) {
  1161.                 $raw $qItem->getOptions();
  1162.                 $opts is_string($raw) ? json_decode($rawtrue) : $raw;
  1163.             }
  1164.             if (is_array($opts) && !empty($opts['print'])) {
  1165.                 // 同一アイテム内で同じKindが複数ヶ所あっても数量は一度だけ加算する
  1166.                 $kindsThisItem = [];
  1167.                 $kindsAndTypesThisItem = [];
  1168.                 foreach ($opts['print'] as $p) {
  1169.                     $kind = isset($p['position']) ? (int)$p['position'] : 0;
  1170.                     if ($kind === 0) { continue; }
  1171.                     
  1172.                     // Get print type for this kind
  1173.                     $printType null;
  1174.                     switch ($kind) {
  1175.                         case 1// silk
  1176.                             $printType $p['print_type'] ?? null;
  1177.                             break;
  1178.                         case 4// magic
  1179.                             $printType $p['magic_type'] ?? null;
  1180.                             break;
  1181.                         case 3// shishu
  1182.                             $printType $p['shishu_type'] ?? null;
  1183.                             break;
  1184.                         case 2// tensha
  1185.                             $printType 'tensha';
  1186.                             break;
  1187.                     }
  1188.                     
  1189.                     // 種別ごとに必須入力を満たす場合のみ採用
  1190.                     $isValid false;
  1191.                     switch ($kind) {
  1192.                         case 1// silk
  1193.                             $isValid = !empty($p['print_type']) && !empty($p['print_detail']);
  1194.                             break;
  1195.                         case 4// magic
  1196.                             $isValid = !empty($p['magic_type']);
  1197.                             break;
  1198.                         case 3// shishu
  1199.                             $isValid = !empty($p['shishu_type']) && !empty($p['shishu_detail']);
  1200.                             break;
  1201.                         case 2// tensha
  1202.                             $isValid = !empty($p['tensha_tate']) && !empty($p['tensha_yoko']);
  1203.                             break;
  1204.                     }
  1205.                     if ($isValid) {
  1206.                         $kindsThisItem[$kind] = true;
  1207.                         // Also track Kind+PrintType combinations using createLotKey logic
  1208.                         $kindTypeKey $this->createLotKey($kind$printType);
  1209.                         $kindsAndTypesThisItem[$kindTypeKey] = true;
  1210.                     }
  1211.                 }
  1212.                 if (!empty($kindsThisItem)) {
  1213.                     // このCartItemは印刷あり → 全体合算数量も加算
  1214.                     $this->LotBaseQuantity += $qty;
  1215.                     foreach (array_keys($kindsThisItem) as $kind) {
  1216.                         if (!isset($this->LotBaseQuantityByKind[$kind])) {
  1217.                             $this->LotBaseQuantityByKind[$kind] = 0;
  1218.                         }
  1219.                         $this->LotBaseQuantityByKind[$kind] += $qty;
  1220.                         // 明示的な4変数にも反映
  1221.                         switch ($kind) {
  1222.                             case 1$this->LotQtyPrint += $qty; break;
  1223.                             case 4$this->LotQtyMagic += $qty; break;
  1224.                             case 3$this->LotQtyShishu += $qty; break;
  1225.                             case 2$this->LotQtyTensha += $qty; break;
  1226.                         }
  1227.                     }
  1228.                     // Also add to Kind+PrintType combinations
  1229.                     foreach (array_keys($kindsAndTypesThisItem) as $kindTypeKey) {
  1230.                         if (!isset($this->LotBaseQuantityByKindAndType[$kindTypeKey])) {
  1231.                             $this->LotBaseQuantityByKindAndType[$kindTypeKey] = 0;
  1232.                         }
  1233.                         $this->LotBaseQuantityByKindAndType[$kindTypeKey] += $qty;
  1234.                     }
  1235.                 }
  1236.             }
  1237.         }
  1238.         foreach ($Items as $Item) {
  1239.             if (!$Item->getOptions()) {
  1240.                 continue;
  1241.             }
  1242.             // このアイテムに印刷があるか/どの種別かを判定し、本明細で許可する種別をセット
  1243.             $opts $Item->getOptions();
  1244.             if (is_string($opts)) { $opts json_decode($optstrue); }
  1245.              // Add product_class_id to options if Item has it
  1246.              if (method_exists($Item'getProductClass') && $Item->getProductClass()) {
  1247.                 $productClass $Item->getProductClass();
  1248.                 if (method_exists($productClass'getId')) {
  1249.                     $opts['product_class_id'] = $productClass->getId();
  1250.                 }
  1251.             }
  1252.             $this->ApplyLotAllowedKind = [];
  1253.             if (is_array($opts) && !empty($opts['print'])) {
  1254.                 foreach ($opts['print'] as $p) {
  1255.                     $kind = isset($p['position']) ? (int)$p['position'] : null;
  1256.                     if (is_null($kind)) { continue; }
  1257.                     
  1258.                     // Get print type for this kind
  1259.                     $printType null;
  1260.                     switch ($kind) {
  1261.                         case 1// シルクプリント
  1262.                             $printType $p['print_type'] ?? null;
  1263.                             break;
  1264.                         case 4// マジック
  1265.                             $printType $p['magic_type'] ?? null;
  1266.                             break;
  1267.                         case 3// 刺繍
  1268.                             $printType $p['shishu_type'] ?? null;
  1269.                             break;
  1270.                         case 2// デジタル転写
  1271.                             $printType 'tensha';
  1272.                             break;
  1273.                     }
  1274.                     
  1275.                     // Create unique key using createLotKey logic
  1276.                     $lotKey $this->createLotKey($kind$printType);
  1277.                     
  1278.                     // 未表示の種別+タイプのみ許可
  1279.                     if (!($this->LotKindAlreadyShown[$lotKey] ?? false)) {
  1280.                         $this->ApplyLotAllowedKind[$kind] = true;
  1281.                     }
  1282.                 }
  1283.             }
  1284.             #OrderItemの時 不要かな?
  1285.             if ('order' == $Page) {
  1286.                 $orderItemTypeId null;
  1287.                 if (method_exists($Item'getOrderItemTypeId')) {
  1288.                     $orderItemTypeId $Item->getOrderItemTypeId();
  1289.                 } elseif (method_exists($Item'getOrderItemType') && $Item->getOrderItemType()) {
  1290.                     $orderItemTypeId $Item->getOrderItemType()->getId();
  1291.                 } else {
  1292.                     // For CartItem, assume it's a product type (type 1) for processing
  1293.                     $orderItemTypeId 1;
  1294.                 }
  1295.                 if (!= $orderItemTypeId) {
  1296.                     $this->ApplyLotAllowedKind = []; // 念のためリセット
  1297.                     continue;
  1298.                 }
  1299.             }
  1300.             #重複するEstimateIdの回避
  1301.             if (isset($Options[$Item->getEstimateId()])) {
  1302.                 $this->ApplyLotAllowedKind = []; // 念のためリセット
  1303.                 continue;
  1304.             }
  1305.             $EstimantTotal $Param[$Item->getEstimateId()] ?? [];
  1306.             if ($Error $this->EstimateCharacterIzation($opts$EstimantTotal$Page)) {
  1307.                 $this->ApplyLotAllowedKind = []; // 念のためリセット
  1308.                 throw new \RuntimeException("見積もりシミュレーションのデータ解析処理に失敗しました\nエラーコード: {$Error}");
  1309.             }
  1310.             $Options[$Item->getEstimateId()] = $this->Characters;
  1311.             $Total += $this->Characters['total'] ?? 0;
  1312.             $Total += $this->Characters['lot']['total'] ?? 0;
  1313.             // このアイテムで表示された種別+タイプを記録
  1314.             if (is_array($opts) && !empty($opts['print'])) {
  1315.                 foreach ($opts['print'] as $p) {
  1316.                     $kind = isset($p['position']) ? (int)$p['position'] : null;
  1317.                     if (is_null($kind)) { continue; }
  1318.                     
  1319.                     // Get print type for this kind
  1320.                     $printType null;
  1321.                     switch ($kind) {
  1322.                         case 1// シルクプリント
  1323.                             $printType $p['print_type'] ?? null;
  1324.                             break;
  1325.                         case 4// マジック
  1326.                             $printType $p['magic_type'] ?? null;
  1327.                             break;
  1328.                         case 3// 刺繍
  1329.                             $printType $p['shishu_type'] ?? null;
  1330.                             break;
  1331.                         case 2// デジタル転写
  1332.                             $printType 'tensha';
  1333.                             break;
  1334.                     }
  1335.                     
  1336.                     // Create unique key using createLotKey logic
  1337.                     $lotKey $this->createLotKey($kind$printType);
  1338.                     
  1339.                     // このアイテムで有効な種別+タイプを記録
  1340.                     if ($this->ApplyLotAllowedKind[$kind] ?? false) {
  1341.                         $this->LotKindAlreadyShown[$lotKey] = true;
  1342.                     }
  1343.                 }
  1344.             }
  1345.             // ループ終端で常にリセット
  1346.             $this->ApplyLotAllowedKind = [];
  1347.         }
  1348.         return [$Total$Options];
  1349.     }
  1350.     protected function SetKatadai($type,$Price,$priceAfterYear null){
  1351.         #リピート注文ではない
  1352.         if (!$this->RepeatDate){return $Price;}
  1353.         $Day =  new \DateTime($this->RepeatDate);
  1354.         $Diff $Day->diff(new \DateTime('now'));
  1355.         $RepeteData$this->CommonService->GetConfig('ESTIMATE_REPEAT');
  1356.         $hikaku 'under';
  1357.         if ($Diff->days $RepeteData['day']){$hikaku 'orver';}
  1358.         $Ritu $RepeteData[$type][$hikaku];
  1359.         $Price $Price *  $Ritu /100;
  1360.         if($priceAfterYear && $hikaku == 'orver') {
  1361.             $Price =  $priceAfterYear;
  1362.         }
  1363.         return $Price;
  1364.     }
  1365.     protected function SetKatadaiType(){
  1366.         #リピート注文ではない
  1367.         if (!$this->RepeatDate){return null;}
  1368.         $Day =  new \DateTime($this->RepeatDate);
  1369.         $Diff $Day->diff(new \DateTime('now'));
  1370.         $RepeteData$this->CommonService->GetConfig('ESTIMATE_REPEAT');
  1371.         $hikaku 'under';
  1372.         if ($Diff->days $RepeteData['day']){$hikaku 'orver';}
  1373.         return $hikaku == 'under' 3;
  1374.     }
  1375.     private function getDelivery($productClassId$Options)
  1376.     {
  1377.         if (!$productClassId) return self::DeliveryId;
  1378.         $CartType CartService::CartTypeEstimate;
  1379.         if ($this->hasSusoage($productClassId) || $this->hasKyouei($productClassId) || $this->hasShishu($Options)) {
  1380.             $CartType CartService::CartTypeNormal;
  1381.         }
  1382.         $DeliverMap $this->YamlService->GetYaml('DeliveryMapping,yaml');
  1383.         $DeliveryId $DeliverMap[$CartType] ?? 1;
  1384.         $Delivery $this->DeliveryRepository->findBy(['id' => $DeliveryId]);
  1385.         return $Delivery $Delivery[0]->getId() : self::DeliveryId;
  1386.     }
  1387.     private function hasSusoage($productClassId)
  1388.     {
  1389.         return GoodsService::isSusoageExistInJanIdList([$productClassId]);
  1390.     }
  1391.     private function hasKyouei($productClassId)
  1392.     {
  1393.         return GoodsService::isKyoueiExistInJanIdList([$productClassId]);
  1394.     }
  1395.     private function hasShishu($Options)
  1396.     {
  1397.         $hasShishu false;
  1398.         if (isset($Options['print']) && is_array($Options['print'])) {
  1399.             $prints $Options['print'];
  1400.             foreach ($prints as $print) {
  1401.                 if ($print['position'] == 3) {
  1402.                     $hasShishu true;
  1403.                 }
  1404.             }
  1405.         }
  1406.         return $hasShishu;
  1407.     }
  1408. }