Completed
Pull Request — experimental/sf (#3444)
by
unknown
744:40 queued 738:08
created

ProductClassController::saveProductClasses()   C

Complexity

Conditions 14
Paths 35

Size

Total Lines 89

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 14.2217

Importance

Changes 0
Metric Value
cc 14
nc 35
nop 2
dl 0
loc 89
ccs 43
cts 48
cp 0.8958
crap 14.2217
rs 5.2533
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Controller\Admin\Product;
15
16
use Doctrine\ORM\NoResultException;
17
use Eccube\Controller\AbstractController;
18
use Eccube\Entity\ClassName;
19
use Eccube\Entity\Product;
20
use Eccube\Entity\ProductClass;
21
use Eccube\Entity\ProductStock;
22
use Eccube\Entity\TaxRule;
23
use Eccube\Form\Type\Admin\ProductClassMatrixType;
24
use Eccube\Repository\BaseInfoRepository;
25
use Eccube\Repository\ClassCategoryRepository;
26
use Eccube\Repository\ProductClassRepository;
27
use Eccube\Repository\ProductRepository;
28
use Eccube\Repository\TaxRuleRepository;
29
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
30
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
31
use Symfony\Component\Form\Extension\Core\Type\FormType;
32
use Symfony\Component\HttpFoundation\Request;
33
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
34
35
class ProductClassController extends AbstractController
36
{
37
    /**
38
     * @var ProductRepository
39
     */
40
    protected $productRepository;
41
42
    /**
43
     * @var ProductClassRepository
44
     */
45
    protected $productClassRepository;
46
47
    /**
48
     * @var ClassCategoryRepository
49
     */
50
    protected $classCategoryRepository;
51
52
    /**
53
     * @var BaseInfoRepository
54
     */
55
    protected $baseInfoRepository;
56
57
    /**
58
     * @var TaxRuleRepository
59
     */
60
    protected $taxRuleRepository;
61
62
    /**
63
     * ProductClassController constructor.
64
     *
65
     * @param ProductClassRepository $productClassRepository
66
     * @param ClassCategoryRepository $classCategoryRepository
67
     */
68 13
    public function __construct(
69
        ProductRepository $productRepository,
70
        ProductClassRepository $productClassRepository,
71
        ClassCategoryRepository $classCategoryRepository,
72
        BaseInfoRepository $baseInfoRepository,
73
        TaxRuleRepository $taxRuleRepository
74
    ) {
75 13
        $this->productRepository = $productRepository;
76 13
        $this->productClassRepository = $productClassRepository;
77 13
        $this->classCategoryRepository = $classCategoryRepository;
78 13
        $this->baseInfoRepository = $baseInfoRepository;
79 13
        $this->taxRuleRepository = $taxRuleRepository;
80
    }
81
82
    /**
83
     * 商品規格が登録されていなければ新規登録, 登録されていれば更新画面を表示する
84
     *
85
     * @Route("/%eccube_admin_route%/product/product/class/{id}", requirements={"id" = "\d+"}, name="admin_product_product_class")
86
     * @Template("@admin/Product/product_class.twig")
87
     */
88 13
    public function index(Request $request, $id)
89
    {
90 13
        $Product = $this->findProduct($id);
91 13
        if (!$Product) {
92
            throw new NotFoundHttpException();
93
        }
94
95 13
        $ClassName1 = null;
96 13
        $ClassName2 = null;
97
98 13
        if ($Product->hasProductClass()) {
99
            // 規格ありの商品は編集画面を表示する.
100 11
            $ProductClasses = $Product->getProductClasses()
101 11
                ->filter(function ($pc) {
102 11
                    return $pc->getClassCategory1() !== null;
103 11
                });
104
105
            // 設定されている規格名1, 2を取得(商品規格の規格分類には必ず同じ値がセットされている)
106 11
            $FirstProductClass = $ProductClasses->first();
107 11
            $ClassName1 = $FirstProductClass->getClassCategory1()->getClassName();
108 11
            $ClassCategory2 = $FirstProductClass->getClassCategory2();
109 11
            $ClassName2 = $ClassCategory2 ? $ClassCategory2->getClassName() : null;
110
111
            // 規格名1/2から組み合わせを生成し, DBから取得した商品規格とマージする.
112 11
            $ProductClasses = $this->mergeProductClassess(
113 11
                $this->createProductClasses($ClassName1, $ClassName2),
114 11
                $ProductClasses);
115
116
            // 組み合わせのフォームを生成する.
117 11
            $form = $this->createMatrixForm($ProductClasses, $ClassName1, $ClassName2,
118 11
                ['product_classes_exist' => true]);
119 11
            $form->handleRequest($request);
120
121 11
            if ($form->isSubmitted() && $form->isValid()) {
122
                // フォームではtokenを無効化しているのでここで確認する.
123 5
                $this->isTokenValid();
124
125 5
                $this->saveProductClasses($Product, $form['product_classes']->getData());
126
127 5
                $this->addSuccess('admin.product.product_class.update.complete', 'admin');
128
129 11
                return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
130
            }
131
        } else {
132
            // 規格なし商品
133 5
            $form = $this->createMatrixForm();
134 5
            $form->handleRequest($request);
135
136 5
            if ($form->isSubmitted() && $form->isValid()) {
137
                // フォームではtokenを無効化しているのでここで確認する.
138 4
                $this->isTokenValid();
139
140
                // 登録,更新ボタンが押下されたかどうか.
141 4
                $isSave = $form['save']->isClicked();
142
143
                // 規格名1/2から商品規格の組み合わせを生成する.
144 4
                $ClassName1 = $form['class_name1']->getData();
145 4
                $ClassName2 = $form['class_name2']->getData();
146 4
                $ProductClasses = $this->createProductClasses($ClassName1, $ClassName2);
147
148
                // 組み合わせのフォームを生成する.
149
                // class_name1, class_name2が取得できるのがsubmit後のため, フォームを再生成して組み合わせ部分を構築している
150
                // submit後だと, フォーム項目の追加やデータ変更が許可されないため.
151 4
                $form = $this->createMatrixForm($ProductClasses, $ClassName1, $ClassName2,
152 4
                    ['product_classes_exist' => true]);
153
154
                // 登録ボタン押下時
155 4
                if ($isSave) {
156 3
                    $form->handleRequest($request);
157 3
                    if ($form->isSubmitted() && $form->isValid()) {
158 3
                        $this->saveProductClasses($Product, $form['product_classes']->getData());
159
160 3
                        $this->addSuccess('admin.product.product_class.save.complete', 'admin');
161
162 3
                        return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
163
                    }
164
                }
165
            }
166
        }
167
168
        return [
169 13
            'Product' => $Product,
170 13
            'form' => $form->createView(),
171 13
            'clearForm' => $this->createForm(FormType::class)->createView(),
172 13
            'ClassName1' => $ClassName1,
173 13
            'ClassName2' => $ClassName2,
174
        ];
175
    }
176
177
    /**
178
     * 商品規格を初期化する.
179
     *
180
     * @Route("/%eccube_admin_route%/product/product/class/{id}/clear", requirements={"id" = "\d+"}, name="admin_product_product_class_clear")
181
     */
182
    public function clearProductClasses(Request $request, Product $Product)
183
    {
184
        if (!$Product->hasProductClass()) {
185
            return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
186
        }
187
188
        $form = $this->createForm(FormType::class);
189
        $form->handleRequest($request);
190
191
        if ($form->isSubmitted() && $form->isValid()) {
192
            $ProductClasses = $this->productClassRepository->findBy([
193
                'Product' => $Product,
194
            ]);
195
196
            // デフォルト規格のみ有効にする
197
            foreach ($ProductClasses as $ProductClass) {
198
                $ProductClass->setVisible(false);
199
            }
200
            foreach ($ProductClasses as $ProductClass) {
201
                if (null === $ProductClass->getClassCategory1() && null === $ProductClass->getClassCategory2()) {
202
                    $ProductClass->setVisible(true);
203
                    break;
204
                }
205
            }
206
207
            $this->entityManager->flush();
208
209
            $this->addSuccess('admin.product.product_class.clear.complete', 'admin');
210
        }
211
212
        return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
213
    }
214
215
    /**
216
     * 規格名1/2から, 商品規格の組み合わせを生成する.
217
     *
218
     * @param ClassName $ClassName1
219
     * @param ClassName|null $ClassName2
220
     *
221
     * @return array|ProductClass[]
222
     */
223 12
    protected function createProductClasses(ClassName $ClassName1, ClassName $ClassName2 = null)
224
    {
225 12
        $ProductClasses = [];
226 12
        $ClassCategories1 = $this->classCategoryRepository->findBy(['ClassName' => $ClassName1], ['sort_no' => 'DESC']);
227 12
        $ClassCategories2 = [];
228 12
        if ($ClassName2) {
229 8
            $ClassCategories2 = $this->classCategoryRepository->findBy(['ClassName' => $ClassName2],
230 8
                ['sort_no' => 'DESC']);
231
        }
232 12
        foreach ($ClassCategories1 as $ClassCategory1) {
233
            // 規格1のみ
234 12
            if (!$ClassName2) {
235 4
                $ProductClass = new ProductClass();
236 4
                $ProductClass->setClassCategory1($ClassCategory1);
237 4
                $ProductClasses[] = $ProductClass;
238 4
                continue;
239
            }
240
            // 規格1/2
241 8
            foreach ($ClassCategories2 as $ClassCategory2) {
242 8
                $ProductClass = new ProductClass();
243 8
                $ProductClass->setClassCategory1($ClassCategory1);
244 8
                $ProductClass->setClassCategory2($ClassCategory2);
245 8
                $ProductClasses[] = $ProductClass;
246
            }
247
        }
248
249 12
        return $ProductClasses;
250
    }
251
252
    /**
253
     * 商品規格の配列をマージする.
254
     *
255
     * @param $ProductClassessForMatrix
256
     * @param $ProductClasses
257
     *
258
     * @return array|ProductClass[]
259
     */
260 11
    protected function mergeProductClassess($ProductClassessForMatrix, $ProductClasses)
261
    {
262 11
        $mergedProductClasses = [];
263 11
        foreach ($ProductClassessForMatrix as $pcfm) {
264 11
            foreach ($ProductClasses as $pc) {
265 11
                if ($pcfm->getClassCategory1()->getId() === $pc->getClassCategory1()->getId()) {
266 11
                    $cc2fm = $pcfm->getClassCategory2();
267 11
                    $cc2 = $pc->getClassCategory2();
268
269 11
                    if (null === $cc2fm && null === $cc2) {
270 3
                        $mergedProductClasses[] = $pc;
271 3
                        continue 2;
272
                    }
273
274 8
                    if ($cc2fm && $cc2 && $cc2fm->getId() === $cc2->getId()) {
275 8
                        $mergedProductClasses[] = $pc;
276 11
                        continue 2;
277
                    }
278
                }
279
            }
280
281 6
            $mergedProductClasses[] = $pcfm;
282
        }
283
284 11
        return $mergedProductClasses;
285
    }
286
287
    /**
288
     * 商品規格を登録, 更新する.
289
     *
290
     * @param Product $Product
291
     * @param array|ProductClass[] $ProductClasses
292
     */
293 8
    protected function saveProductClasses(Product $Product, $ProductClasses = [])
294
    {
295 8
        foreach ($ProductClasses as $pc) {
296
            // 新規登録時、チェックを入れていなければ更新しない
297 8
            if (!$pc->getId() && !$pc->isVisible()) {
298 4
                continue;
299
            }
300
301
            // 無効から有効にした場合は, 過去の登録情報を検索.
302 8
            if (!$pc->getId()) {
303
                /** @var ProductClass $ExistsProductClass */
304 4
                $ExistsProductClass = $this->productClassRepository->findOneBy([
305 4
                    'Product' => $Product,
306 4
                    'ClassCategory1' => $pc->getClassCategory1(),
307 4
                    'ClassCategory2' => $pc->getClassCategory2(),
308
                ]);
309
310
                // 過去の登録情報があればその情報を復旧する.
311 4
                if ($ExistsProductClass) {
312
                    $ExistsProductClass->copyProperties($pc, [
313
                        'id',
314
                        'price01_inc_tax',
315
                        'price02_inc_tax',
316
                        'create_date',
317
                        'update_date',
318
                        'Creator',
319
                    ]);
320
                    $pc = $ExistsProductClass;
321
                }
322
            }
323
324
            // 更新時, チェックを外した場合はPOST内容を破棄してvisibleのみ更新する.
325 8
            if ($pc->getId() && !$pc->isVisible()) {
326 1
                $this->entityManager->refresh($pc);
327 1
                $pc->setVisible(false);
328 1
                continue;
329
            }
330
331 8
            $pc->setProduct($Product);
332 8
            $this->entityManager->persist($pc);
333
334
            // 在庫の更新
335 8
            $ProductStock = $pc->getProductStock();
336 8
            if (!$ProductStock) {
337 4
                $ProductStock = new ProductStock();
338 4
                $ProductStock->setProductClass($pc);
339 4
                $this->entityManager->persist($ProductStock);
340
            }
341 8
            $ProductStock->setStock($pc->isStockUnlimited() ? null : $pc->getStock());
342
343 8
            if ($this->baseInfoRepository->get()->isOptionProductTaxRule()) {
344 6
                $rate = $pc->getTaxRate();
345 6
                $TaxRule = $pc->getTaxRule();
346 6
                if (is_numeric($rate)) {
347 5
                    if ($TaxRule) {
348
                        $TaxRule->setTaxRate($rate);
349
                    } else {
350
                        // 初期税率設定の計算方法を設定する
351 5
                        $RoundingType = $this->taxRuleRepository->find(TaxRule::DEFAULT_TAX_RULE_ID)
352 5
                            ->getRoundingType();
353
354 5
                        $TaxRule = new TaxRule();
355 5
                        $TaxRule->setProduct($Product);
356 5
                        $TaxRule->setProductClass($pc);
357 5
                        $TaxRule->setTaxRate($rate);
358 5
                        $TaxRule->setRoundingType($RoundingType);
359 5
                        $TaxRule->setTaxAdjust(0);
360 5
                        $TaxRule->setApplyDate(new \DateTime());
361 5
                        $this->entityManager->persist($TaxRule);
362
                    }
363
                } else {
364 4
                    if ($TaxRule) {
365
                        $this->taxRuleRepository->delete($TaxRule);
366 8
                        $pc->setTaxRule(null);
367
                    }
368
                }
369
            }
370
        }
371
372
        // デフォルト規格を非表示にする.
373 8
        $DefaultProductClass = $this->productClassRepository->findOneBy([
374 8
            'Product' => $Product,
375
            'ClassCategory1' => null,
376
            'ClassCategory2' => null,
377
        ]);
378 8
        $DefaultProductClass->setVisible(false);
379
380 8
        $this->entityManager->flush();
381
    }
382
383
    /**
384
     * 商品規格登録フォームを生成する.
385
     *
386
     * @param array $ProductClasses
387
     * @param ClassName|null $ClassName1
388
     * @param ClassName|null $ClassName2
389
     * @param array $options
390
     *
391
     * @return \Symfony\Component\Form\FormInterface
392
     */
393 13
    protected function createMatrixForm(
394
        $ProductClasses = [],
395
        ClassName $ClassName1 = null,
396
        ClassName $ClassName2 = null,
397
        array $options = []
398
    ) {
399 13
        $options = array_merge(['csrf_protection' => false], $options);
400 13
        $builder = $this->formFactory->createBuilder(ProductClassMatrixType::class, [
401 13
            'product_classes' => $ProductClasses,
402 13
            'class_name1' => $ClassName1,
403 13
            'class_name2' => $ClassName2,
404 13
        ], $options);
405
406 13
        return $builder->getForm();
407
    }
408
409
    /**
410
     * 商品を取得する.
411
     * 商品規格はvisible=trueのものだけを取得し, 規格分類はsort_no=DESCでソートされている.
412
     *
413
     * @param $id
414
     *
415
     * @return Product|null
416
     *
417
     * @throws \Doctrine\ORM\NonUniqueResultException
418
     */
419 13
    protected function findProduct($id)
420
    {
421 13
        $qb = $this->productRepository->createQueryBuilder('p')
422 13
            ->addSelect(['pc', 'cc1', 'cc2'])
423 13
            ->leftJoin('p.ProductClasses', 'pc')
424 13
            ->leftJoin('pc.ClassCategory1', 'cc1')
425 13
            ->leftJoin('pc.ClassCategory2', 'cc2')
426 13
            ->where('p.id = :id')
427 13
            ->andWhere('pc.visible = :pc_visible')
428 13
            ->setParameter('id', $id)
429 13
            ->setParameter('pc_visible', true)
430 13
            ->orderBy('cc1.sort_no', 'DESC')
431 13
            ->addOrderBy('cc2.sort_no', 'DESC');
432
433
        try {
434 13
            return $qb->getQuery()->getSingleResult();
435
        } catch (NoResultException $e) {
436
            return null;
437
        }
438
    }
439
}
440