Failed Conditions
Pull Request — 4.0 (#3791)
by Ryo
07:54
created

ProductClassController::mergeProductClasses()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
nc 6
nop 2
dl 0
loc 26
ccs 9
cts 9
cp 1
crap 9
rs 8.0555
c 0
b 0
f 0
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\Template;
30
use Symfony\Component\Form\Extension\Core\Type\FormType;
31
use Symfony\Component\HttpFoundation\Request;
32
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
33
use Symfony\Component\Routing\Annotation\Route;
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->mergeProductClasses(
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 View Code Duplication
            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.common.save_complete', 'admin');
128
129 11
                if ($request->get('return')) {
130
                    return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId(), 'return' => $request->get('return')]);
131
                }
132
133 5
                return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
134 5
            }
135
        } else {
136 5
            // 規格なし商品
137
            $form = $this->createMatrixForm();
138 4
            $form->handleRequest($request);
139
140
            if ($form->isSubmitted() && $form->isValid()) {
141 4
                // フォームではtokenを無効化しているのでここで確認する.
142
                $this->isTokenValid();
143
144 4
                // 登録,更新ボタンが押下されたかどうか.
145 4
                $isSave = $form['save']->isClicked();
146 4
147
                // 規格名1/2から商品規格の組み合わせを生成する.
148
                $ClassName1 = $form['class_name1']->getData();
149
                $ClassName2 = $form['class_name2']->getData();
150
                $ProductClasses = $this->createProductClasses($ClassName1, $ClassName2);
151 4
152 4
                // 組み合わせのフォームを生成する.
153
                // class_name1, class_name2が取得できるのがsubmit後のため, フォームを再生成して組み合わせ部分を構築している
154
                // submit後だと, フォーム項目の追加やデータ変更が許可されないため.
155 4
                $form = $this->createMatrixForm($ProductClasses, $ClassName1, $ClassName2,
156 3
                    ['product_classes_exist' => true]);
157 3
158 3
                // 登録ボタン押下時
159
                if ($isSave) {
160 3
                    $form->handleRequest($request);
161 View Code Duplication
                    if ($form->isSubmitted() && $form->isValid()) {
162 3
                        $this->saveProductClasses($Product, $form['product_classes']->getData());
163
164
                        $this->addSuccess('admin.common.save_complete', 'admin');
165
166
                        if ($request->get('return')) {
167
                            return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId(), 'return' => $request->get('return')]);
168
                        }
169 13
170 13
                        return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
171 13
                    }
172 13
                }
173 13
            }
174
        }
175
176
        return [
177
            'Product' => $Product,
178
            'form' => $form->createView(),
179
            'clearForm' => $this->createForm(FormType::class)->createView(),
180
            'ClassName1' => $ClassName1,
181
            'ClassName2' => $ClassName2,
182
            'return_product' => $request->get('return'),
183
        ];
184
    }
185
186
    /**
187
     * 商品規格を初期化する.
188
     *
189
     * @Route("/%eccube_admin_route%/product/product/class/{id}/clear", requirements={"id" = "\d+"}, name="admin_product_product_class_clear")
190
     */
191
    public function clearProductClasses(Request $request, Product $Product)
192
    {
193
        if (!$Product->hasProductClass()) {
194
            return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
195
        }
196
197
        $form = $this->createForm(FormType::class);
198
        $form->handleRequest($request);
199
200
        if ($form->isSubmitted() && $form->isValid()) {
201
            $ProductClasses = $this->productClassRepository->findBy([
202
                'Product' => $Product,
203
            ]);
204
205
            // デフォルト規格のみ有効にする
206
            foreach ($ProductClasses as $ProductClass) {
207
                $ProductClass->setVisible(false);
208
            }
209
            foreach ($ProductClasses as $ProductClass) {
210
                if (null === $ProductClass->getClassCategory1() && null === $ProductClass->getClassCategory2()) {
211
                    $ProductClass->setVisible(true);
212
                    break;
213
                }
214
            }
215
216
            $this->entityManager->flush();
217
218
            $this->addSuccess('admin.product.reset_complete', 'admin');
219
        }
220
221
        if ($request->get('return')) {
222
            return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId(), 'return' => $request->get('return')]);
223 12
        }
224
225 12
        return $this->redirectToRoute('admin_product_product_class', ['id' => $Product->getId()]);
226 12
    }
227 12
228 12
    /**
229 8
     * 規格名1/2から, 商品規格の組み合わせを生成する.
230 8
     *
231
     * @param ClassName $ClassName1
232 12
     * @param ClassName|null $ClassName2
233
     *
234 12
     * @return array|ProductClass[]
235 5
     */
236 5
    protected function createProductClasses(ClassName $ClassName1, ClassName $ClassName2 = null)
237 5
    {
238 5
        $ProductClasses = [];
239
        $ClassCategories1 = $this->classCategoryRepository->findBy(['ClassName' => $ClassName1], ['sort_no' => 'DESC']);
240
        $ClassCategories2 = [];
241 8
        if ($ClassName2) {
242 8
            $ClassCategories2 = $this->classCategoryRepository->findBy(['ClassName' => $ClassName2],
243 8
                ['sort_no' => 'DESC']);
244 8
        }
245 8
        foreach ($ClassCategories1 as $ClassCategory1) {
246
            // 規格1のみ
247
            if (!$ClassName2) {
248
                $ProductClass = new ProductClass();
249 12
                $ProductClass->setClassCategory1($ClassCategory1);
250
                $ProductClasses[] = $ProductClass;
251
                continue;
252
            }
253
            // 規格1/2
254
            foreach ($ClassCategories2 as $ClassCategory2) {
255
                $ProductClass = new ProductClass();
256
                $ProductClass->setClassCategory1($ClassCategory1);
257
                $ProductClass->setClassCategory2($ClassCategory2);
258
                $ProductClasses[] = $ProductClass;
259
            }
260 11
        }
261
262 11
        return $ProductClasses;
263 11
    }
264 11
265 11
    /**
266 11
     * 商品規格の配列をマージする.
267 11
     *
268
     * @param $ProductClassesForMatrix
269 11
     * @param $ProductClasses
270 4
     *
271 4
     * @return array|ProductClass[]
272
     */
273
    protected function mergeProductClasses($ProductClassesForMatrix, $ProductClasses)
274 8
    {
275 8
        $mergedProductClasses = [];
276 11
        foreach ($ProductClassesForMatrix as $pcfm) {
277
            foreach ($ProductClasses as $pc) {
278
                if ($pcfm->getClassCategory1()->getId() === $pc->getClassCategory1()->getId()) {
279
                    $cc2fm = $pcfm->getClassCategory2();
280
                    $cc2 = $pc->getClassCategory2();
281 6
282
                    if (null === $cc2fm && null === $cc2) {
283
                        $mergedProductClasses[] = $pc;
284 11
                        continue 2;
285
                    }
286
287
                    if ($cc2fm && $cc2 && $cc2fm->getId() === $cc2->getId()) {
288
                        $mergedProductClasses[] = $pc;
289
                        continue 2;
290
                    }
291
                }
292
            }
293 8
294
            $mergedProductClasses[] = $pcfm;
295 8
        }
296
297 8
        return $mergedProductClasses;
298 4
    }
299
300
    /**
301
     * 商品規格を登録, 更新する.
302 8
     *
303
     * @param Product $Product
304 4
     * @param array|ProductClass[] $ProductClasses
305 4
     */
306 4
    protected function saveProductClasses(Product $Product, $ProductClasses = [])
307 4
    {
308
        foreach ($ProductClasses as $pc) {
309
            // 新規登録時、チェックを入れていなければ更新しない
310
            if (!$pc->getId() && !$pc->isVisible()) {
311 4
                continue;
312 1
            }
313 1
314
            // 無効から有効にした場合は, 過去の登録情報を検索.
315
            if (!$pc->getId()) {
316
                /** @var ProductClass $ExistsProductClass */
317
                $ExistsProductClass = $this->productClassRepository->findOneBy([
318
                    'Product' => $Product,
319
                    'ClassCategory1' => $pc->getClassCategory1(),
320 1
                    'ClassCategory2' => $pc->getClassCategory2(),
321
                ]);
322
323
                // 過去の登録情報があればその情報を復旧する.
324
                if ($ExistsProductClass) {
325 8
                    $ExistsProductClass->copyProperties($pc, [
326 1
                        'id',
327 1
                        'price01_inc_tax',
328 1
                        'price02_inc_tax',
329
                        'create_date',
330
                        'update_date',
331 8
                        'Creator',
332 8
                    ]);
333
                    $pc = $ExistsProductClass;
334
                }
335 8
            }
336 8
337 4
            // 更新時, チェックを外した場合はPOST内容を破棄してvisibleのみ更新する.
338 4
            if ($pc->getId() && !$pc->isVisible()) {
339 4
                $this->entityManager->refresh($pc);
340
                $pc->setVisible(false);
341 8
                continue;
342
            }
343 8
344 6
            $pc->setProduct($Product);
345 6
            $this->entityManager->persist($pc);
346 6
347 5
            // 在庫の更新
348
            $ProductStock = $pc->getProductStock();
349
            if (!$ProductStock) {
350
                $ProductStock = new ProductStock();
351 5
                $ProductStock->setProductClass($pc);
352 5
                $this->entityManager->persist($ProductStock);
353
            }
354 5
            $ProductStock->setStock($pc->isStockUnlimited() ? null : $pc->getStock());
355 5
356 5
            if ($this->baseInfoRepository->get()->isOptionProductTaxRule()) {
357 5
                $rate = $pc->getTaxRate();
358 5
                $TaxRule = $pc->getTaxRule();
359 5
                if (is_numeric($rate)) {
360 5
                    if ($TaxRule) {
361 5
                        $TaxRule->setTaxRate($rate);
362
                    } else {
363
                        // 初期税率設定の計算方法を設定する
364 4
                        $RoundingType = $this->taxRuleRepository->find(TaxRule::DEFAULT_TAX_RULE_ID)
365
                            ->getRoundingType();
366 8
367
                        $TaxRule = new TaxRule();
368
                        $TaxRule->setProduct($Product);
369
                        $TaxRule->setProductClass($pc);
370
                        $TaxRule->setTaxRate($rate);
371
                        $TaxRule->setRoundingType($RoundingType);
372
                        $TaxRule->setTaxAdjust(0);
373 8
                        $TaxRule->setApplyDate(new \DateTime());
374 8
                        $this->entityManager->persist($TaxRule);
375
                    }
376
                } else {
377
                    if ($TaxRule) {
378 8
                        $this->taxRuleRepository->delete($TaxRule);
379
                        $pc->setTaxRule(null);
380 8
                    }
381
                }
382
            }
383
        }
384
385
        // デフォルト規格を非表示にする.
386
        $DefaultProductClass = $this->productClassRepository->findOneBy([
387
            'Product' => $Product,
388
            'ClassCategory1' => null,
389
            'ClassCategory2' => null,
390
        ]);
391
        $DefaultProductClass->setVisible(false);
392
393 13
        $this->entityManager->flush();
394
    }
395
396
    /**
397
     * 商品規格登録フォームを生成する.
398
     *
399 13
     * @param array $ProductClasses
400 13
     * @param ClassName|null $ClassName1
401 13
     * @param ClassName|null $ClassName2
402 13
     * @param array $options
403 13
     *
404 13
     * @return \Symfony\Component\Form\FormInterface
405
     */
406 13
    protected function createMatrixForm(
407
        $ProductClasses = [],
408
        ClassName $ClassName1 = null,
409
        ClassName $ClassName2 = null,
410
        array $options = []
411
    ) {
412
        $options = array_merge(['csrf_protection' => false], $options);
413
        $builder = $this->formFactory->createBuilder(ProductClassMatrixType::class, [
414
            'product_classes' => $ProductClasses,
415
            'class_name1' => $ClassName1,
416
            'class_name2' => $ClassName2,
417
        ], $options);
418
419 13
        return $builder->getForm();
420
    }
421 13
422 13
    /**
423 13
     * 商品を取得する.
424 13
     * 商品規格はvisible=trueのものだけを取得し, 規格分類はsort_no=DESCでソートされている.
425 13
     *
426 13
     * @param $id
427 13
     *
428 13
     * @return Product|null
429 13
     *
430 13
     * @throws \Doctrine\ORM\NonUniqueResultException
431 13
     */
432
    protected function findProduct($id)
433
    {
434 13
        $qb = $this->productRepository->createQueryBuilder('p')
435
            ->addSelect(['pc', 'cc1', 'cc2'])
436
            ->leftJoin('p.ProductClasses', 'pc')
437
            ->leftJoin('pc.ClassCategory1', 'cc1')
438
            ->leftJoin('pc.ClassCategory2', 'cc2')
439
            ->where('p.id = :id')
440
            ->andWhere('pc.visible = :pc_visible')
441
            ->setParameter('id', $id)
442
            ->setParameter('pc_visible', true)
443
            ->orderBy('cc1.sort_no', 'DESC')
444
            ->addOrderBy('cc2.sort_no', 'DESC');
445
446
        try {
447
            return $qb->getQuery()->getSingleResult();
448
        } catch (NoResultException $e) {
449
            return null;
450
        }
451
    }
452
}
453