diff --git a/e2e/tests/admin-basicinfo.spec.ts b/e2e/tests/admin-basicinfo.spec.ts index 63b2335cf4b..43fcd545707 100644 --- a/e2e/tests/admin-basicinfo.spec.ts +++ b/e2e/tests/admin-basicinfo.spec.ts @@ -1325,4 +1325,65 @@ test.describe('Admin Basic Info (EA07)', () => { await page.waitForLoadState('load'); await expect(page.locator('#page_admin_setting_shop_calendar .alert-success')).toContainText('保存しました'); }); + + test('basicinfo_納品書PDFの出力項目トグル - EA0701-UC01-T15', async ({ page }) => { + // dev 環境の Symfony デバッグツールバーが送信ボタンを覆うことがあるため非表示にする + // (CI の test 環境ではツールバー自体が存在しないため no-op)。 + const hideDebugToolbar = () => page.addStyleTag({ + content: '.sf-toolbar, .sf-minitoolbar { display: none !important; }', + }).catch(() => { /* ツールバーが無い環境では無視 */ }); + + await page.goto(`/${adminRoute}/setting/shop`); + await page.waitForLoadState('load'); + await ensureAdminLoggedIn(page); + if (!page.url().includes('/setting/shop')) { + await page.goto(`/${adminRoute}/setting/shop`); + await page.waitForLoadState('load'); + } + await hideDebugToolbar(); + + // 納品書PDFの出力項目セクションが表示されていること + const hourCheckbox = page.locator('#shop_master_order_pdf_visible_business_hour'); + const invoiceCheckbox = page.locator('#shop_master_order_pdf_visible_invoice_number'); + await expect(hourCheckbox).toBeAttached(); + await expect(invoiceCheckbox).toBeAttached(); + + // 初期状態を退避(アサーション失敗時も finally で必ず復元する) + const hourInitial = await hourCheckbox.isChecked(); + const invoiceInitial = await invoiceCheckbox.isChecked(); + + try { + // 既定 OFF の「店舗営業時間」と既定 ON の「インボイス登録番号」を反転して保存 + await page.locator('label[for="shop_master_order_pdf_visible_business_hour"]').click(); + await page.locator('label[for="shop_master_order_pdf_visible_invoice_number"]').click(); + await page.waitForTimeout(500); + await page.locator('button.ladda-button[type="submit"]').click(); + await page.waitForLoadState('load'); + await expect(page.locator('.alert-success')).toContainText('保存しました', { timeout: 30_000 }); + + // 再表示して反転後の状態が保持されていること + await page.goto(`/${adminRoute}/setting/shop`); + await page.waitForLoadState('load'); + await hideDebugToolbar(); + await expect(page.locator('#shop_master_order_pdf_visible_business_hour')).toBeChecked({ checked: !hourInitial }); + await expect(page.locator('#shop_master_order_pdf_visible_invoice_number')).toBeChecked({ checked: !invoiceInitial }); + } finally { + // 初期状態へ復元して保存(アサーションが失敗しても設定を元に戻す) + await page.goto(`/${adminRoute}/setting/shop`); + await page.waitForLoadState('load'); + await hideDebugToolbar(); + const hourNow = await page.locator('#shop_master_order_pdf_visible_business_hour').isChecked(); + if (hourNow !== hourInitial) { + await page.locator('label[for="shop_master_order_pdf_visible_business_hour"]').click(); + } + const invoiceNow = await page.locator('#shop_master_order_pdf_visible_invoice_number').isChecked(); + if (invoiceNow !== invoiceInitial) { + await page.locator('label[for="shop_master_order_pdf_visible_invoice_number"]').click(); + } + await page.waitForTimeout(500); + await page.locator('button.ladda-button[type="submit"]').click(); + await page.waitForLoadState('load'); + await expect(page.locator('.alert-success')).toContainText('保存しました', { timeout: 30_000 }); + } + }); }); diff --git a/src/Eccube/Entity/BaseInfo.php b/src/Eccube/Entity/BaseInfo.php index 064dc6d5b1c..f69552751c7 100644 --- a/src/Eccube/Entity/BaseInfo.php +++ b/src/Eccube/Entity/BaseInfo.php @@ -170,6 +170,42 @@ class BaseInfo extends AbstractEntity #[ORM\Column(name: 'ucp_catalog_requires_auth', type: Types::BOOLEAN, options: ['default' => false])] private bool $ucp_catalog_requires_auth = false; + // 納品書PDFの店舗情報の出力項目トグル(#6197)。 + // 既定は「現状出力している項目+インボイス要件の会社名」を ON、新規項目は OFF とし、 + // アップグレード時も既存の納品書の見た目を極力維持する。 + #[ORM\Column(name: 'order_pdf_visible_shop_name', type: Types::BOOLEAN, options: ['default' => true])] + private bool $order_pdf_visible_shop_name = true; + + #[ORM\Column(name: 'order_pdf_visible_shop_kana', type: Types::BOOLEAN, options: ['default' => false])] + private bool $order_pdf_visible_shop_kana = false; + + #[ORM\Column(name: 'order_pdf_visible_shop_name_eng', type: Types::BOOLEAN, options: ['default' => false])] + private bool $order_pdf_visible_shop_name_eng = false; + + #[ORM\Column(name: 'order_pdf_visible_address', type: Types::BOOLEAN, options: ['default' => true])] + private bool $order_pdf_visible_address = true; + + #[ORM\Column(name: 'order_pdf_visible_company_name', type: Types::BOOLEAN, options: ['default' => true])] + private bool $order_pdf_visible_company_name = true; + + #[ORM\Column(name: 'order_pdf_visible_company_kana', type: Types::BOOLEAN, options: ['default' => false])] + private bool $order_pdf_visible_company_kana = false; + + #[ORM\Column(name: 'order_pdf_visible_phone_number', type: Types::BOOLEAN, options: ['default' => true])] + private bool $order_pdf_visible_phone_number = true; + + #[ORM\Column(name: 'order_pdf_visible_business_hour', type: Types::BOOLEAN, options: ['default' => false])] + private bool $order_pdf_visible_business_hour = false; + + #[ORM\Column(name: 'order_pdf_visible_email', type: Types::BOOLEAN, options: ['default' => true])] + private bool $order_pdf_visible_email = true; + + #[ORM\Column(name: 'order_pdf_visible_invoice_number', type: Types::BOOLEAN, options: ['default' => true])] + private bool $order_pdf_visible_invoice_number = true; + + #[ORM\Column(name: 'order_pdf_visible_message', type: Types::BOOLEAN, options: ['default' => false])] + private bool $order_pdf_visible_message = false; + /** * Get id. * @@ -937,4 +973,202 @@ public function isUcpCatalogRequiresAuth(): bool { return $this->ucp_catalog_requires_auth; } + + /** + * Set orderPdfVisibleShopName. + */ + public function setOrderPdfVisibleShopName(bool $orderPdfVisibleShopName): BaseInfo + { + $this->order_pdf_visible_shop_name = $orderPdfVisibleShopName; + + return $this; + } + + /** + * Get orderPdfVisibleShopName. + */ + public function isOrderPdfVisibleShopName(): bool + { + return $this->order_pdf_visible_shop_name; + } + + /** + * Set orderPdfVisibleShopKana. + */ + public function setOrderPdfVisibleShopKana(bool $orderPdfVisibleShopKana): BaseInfo + { + $this->order_pdf_visible_shop_kana = $orderPdfVisibleShopKana; + + return $this; + } + + /** + * Get orderPdfVisibleShopKana. + */ + public function isOrderPdfVisibleShopKana(): bool + { + return $this->order_pdf_visible_shop_kana; + } + + /** + * Set orderPdfVisibleShopNameEng. + */ + public function setOrderPdfVisibleShopNameEng(bool $orderPdfVisibleShopNameEng): BaseInfo + { + $this->order_pdf_visible_shop_name_eng = $orderPdfVisibleShopNameEng; + + return $this; + } + + /** + * Get orderPdfVisibleShopNameEng. + */ + public function isOrderPdfVisibleShopNameEng(): bool + { + return $this->order_pdf_visible_shop_name_eng; + } + + /** + * Set orderPdfVisibleAddress. + */ + public function setOrderPdfVisibleAddress(bool $orderPdfVisibleAddress): BaseInfo + { + $this->order_pdf_visible_address = $orderPdfVisibleAddress; + + return $this; + } + + /** + * Get orderPdfVisibleAddress. + */ + public function isOrderPdfVisibleAddress(): bool + { + return $this->order_pdf_visible_address; + } + + /** + * Set orderPdfVisibleCompanyName. + */ + public function setOrderPdfVisibleCompanyName(bool $orderPdfVisibleCompanyName): BaseInfo + { + $this->order_pdf_visible_company_name = $orderPdfVisibleCompanyName; + + return $this; + } + + /** + * Get orderPdfVisibleCompanyName. + */ + public function isOrderPdfVisibleCompanyName(): bool + { + return $this->order_pdf_visible_company_name; + } + + /** + * Set orderPdfVisibleCompanyKana. + */ + public function setOrderPdfVisibleCompanyKana(bool $orderPdfVisibleCompanyKana): BaseInfo + { + $this->order_pdf_visible_company_kana = $orderPdfVisibleCompanyKana; + + return $this; + } + + /** + * Get orderPdfVisibleCompanyKana. + */ + public function isOrderPdfVisibleCompanyKana(): bool + { + return $this->order_pdf_visible_company_kana; + } + + /** + * Set orderPdfVisiblePhoneNumber. + */ + public function setOrderPdfVisiblePhoneNumber(bool $orderPdfVisiblePhoneNumber): BaseInfo + { + $this->order_pdf_visible_phone_number = $orderPdfVisiblePhoneNumber; + + return $this; + } + + /** + * Get orderPdfVisiblePhoneNumber. + */ + public function isOrderPdfVisiblePhoneNumber(): bool + { + return $this->order_pdf_visible_phone_number; + } + + /** + * Set orderPdfVisibleBusinessHour. + */ + public function setOrderPdfVisibleBusinessHour(bool $orderPdfVisibleBusinessHour): BaseInfo + { + $this->order_pdf_visible_business_hour = $orderPdfVisibleBusinessHour; + + return $this; + } + + /** + * Get orderPdfVisibleBusinessHour. + */ + public function isOrderPdfVisibleBusinessHour(): bool + { + return $this->order_pdf_visible_business_hour; + } + + /** + * Set orderPdfVisibleEmail. + */ + public function setOrderPdfVisibleEmail(bool $orderPdfVisibleEmail): BaseInfo + { + $this->order_pdf_visible_email = $orderPdfVisibleEmail; + + return $this; + } + + /** + * Get orderPdfVisibleEmail. + */ + public function isOrderPdfVisibleEmail(): bool + { + return $this->order_pdf_visible_email; + } + + /** + * Set orderPdfVisibleInvoiceNumber. + */ + public function setOrderPdfVisibleInvoiceNumber(bool $orderPdfVisibleInvoiceNumber): BaseInfo + { + $this->order_pdf_visible_invoice_number = $orderPdfVisibleInvoiceNumber; + + return $this; + } + + /** + * Get orderPdfVisibleInvoiceNumber. + */ + public function isOrderPdfVisibleInvoiceNumber(): bool + { + return $this->order_pdf_visible_invoice_number; + } + + /** + * Set orderPdfVisibleMessage. + */ + public function setOrderPdfVisibleMessage(bool $orderPdfVisibleMessage): BaseInfo + { + $this->order_pdf_visible_message = $orderPdfVisibleMessage; + + return $this; + } + + /** + * Get orderPdfVisibleMessage. + */ + public function isOrderPdfVisibleMessage(): bool + { + return $this->order_pdf_visible_message; + } } diff --git a/src/Eccube/Form/Type/Admin/ShopMasterType.php b/src/Eccube/Form/Type/Admin/ShopMasterType.php index eb5fa362bb7..b4d840981a9 100644 --- a/src/Eccube/Form/Type/Admin/ShopMasterType.php +++ b/src/Eccube/Form/Type/Admin/ShopMasterType.php @@ -225,6 +225,18 @@ public function buildForm(FormBuilderInterface $builder, array $options): void // エージェントコマース checkout の有効化フラグ (discovery / catalog は常時公開、checkout のみ制御) ->add('acp_checkout_enabled', ToggleSwitchType::class) ->add('ucp_checkout_enabled', ToggleSwitchType::class) + // 納品書PDFの店舗情報出力項目トグル(#6197)。並び順は納品書の描画順にそろえる + ->add('order_pdf_visible_shop_name', ToggleSwitchType::class) + ->add('order_pdf_visible_shop_kana', ToggleSwitchType::class) + ->add('order_pdf_visible_shop_name_eng', ToggleSwitchType::class) + ->add('order_pdf_visible_address', ToggleSwitchType::class) + ->add('order_pdf_visible_company_name', ToggleSwitchType::class) + ->add('order_pdf_visible_company_kana', ToggleSwitchType::class) + ->add('order_pdf_visible_phone_number', ToggleSwitchType::class) + ->add('order_pdf_visible_business_hour', ToggleSwitchType::class) + ->add('order_pdf_visible_email', ToggleSwitchType::class) + ->add('order_pdf_visible_invoice_number', ToggleSwitchType::class) + ->add('order_pdf_visible_message', ToggleSwitchType::class) ; $builder->add( diff --git a/src/Eccube/Resource/locale/messages.en.yaml b/src/Eccube/Resource/locale/messages.en.yaml index a3f34448b1b..e50abc1fb2c 100644 --- a/src/Eccube/Resource/locale/messages.en.yaml +++ b/src/Eccube/Resource/locale/messages.en.yaml @@ -1231,6 +1231,19 @@ admin.setting.shop.shop.agent_commerce: "Agent Commerce" admin.setting.shop.shop.agent_commerce.description: "Enable checkout via AI agents (ChatGPT / Gemini, etc.). Discovery and catalog are always public; only checkout is controlled here. Checkout is not yet available in Japan, so normally leave these disabled." admin.setting.shop.shop.agent_commerce.acp_checkout_enabled: "Enable ACP checkout" admin.setting.shop.shop.agent_commerce.ucp_checkout_enabled: "Enable UCP checkout" +admin.setting.shop.shop.order_pdf: "Delivery slip PDF items" +admin.setting.shop.shop.order_pdf.description: "Choose the items to print in the shop information area of the delivery slip PDF. Only the items turned ON are printed, using the values entered in the basic settings (items with an empty value are not printed)." +admin.setting.shop.shop.order_pdf.shop_name: "Shop name" +admin.setting.shop.shop.order_pdf.shop_kana: "Shop name (kana)" +admin.setting.shop.shop.order_pdf.shop_name_eng: "Shop name (English)" +admin.setting.shop.shop.order_pdf.address: "Postal code / address" +admin.setting.shop.shop.order_pdf.company_name: "Company name" +admin.setting.shop.shop.order_pdf.company_kana: "Company name (kana)" +admin.setting.shop.shop.order_pdf.phone_number: "Phone number" +admin.setting.shop.shop.order_pdf.business_hour: "Business hours" +admin.setting.shop.shop.order_pdf.email: "Email address" +admin.setting.shop.shop.order_pdf.invoice_number: "Invoice registration number" +admin.setting.shop.shop.order_pdf.message: "Message from shop" #------------------------------------------------------------------------------------ # Settings:Store Settings:Trade Law Settings diff --git a/src/Eccube/Resource/locale/messages.ja.yaml b/src/Eccube/Resource/locale/messages.ja.yaml index 9e16c989b3c..f34ecf99721 100644 --- a/src/Eccube/Resource/locale/messages.ja.yaml +++ b/src/Eccube/Resource/locale/messages.ja.yaml @@ -1230,6 +1230,19 @@ admin.setting.shop.shop.agent_commerce: "エージェントコマース" admin.setting.shop.shop.agent_commerce.description: "AIエージェント (ChatGPT / Gemini 等) 経由のチェックアウトを有効化します。Discovery / カタログは常時公開され、ここでは checkout のみを制御します。checkout は日本未提供のため、通常は無効のままにしてください。" admin.setting.shop.shop.agent_commerce.acp_checkout_enabled: "ACP チェックアウトを有効にする" admin.setting.shop.shop.agent_commerce.ucp_checkout_enabled: "UCP チェックアウトを有効にする" +admin.setting.shop.shop.order_pdf: "納品書PDFの出力項目" +admin.setting.shop.shop.order_pdf.description: "納品書PDFの店舗情報欄に出力する項目を選びます。基本設定で入力済みの値のうち、ONにした項目だけが出力されます(値が空の項目は出力されません)。" +admin.setting.shop.shop.order_pdf.shop_name: "店名" +admin.setting.shop.shop.order_pdf.shop_kana: "店名(カナ)" +admin.setting.shop.shop.order_pdf.shop_name_eng: "店名(英語表記)" +admin.setting.shop.shop.order_pdf.address: "郵便番号・住所" +admin.setting.shop.shop.order_pdf.company_name: "会社名" +admin.setting.shop.shop.order_pdf.company_kana: "会社名(カナ)" +admin.setting.shop.shop.order_pdf.phone_number: "電話番号" +admin.setting.shop.shop.order_pdf.business_hour: "店舗営業時間" +admin.setting.shop.shop.order_pdf.email: "メールアドレス" +admin.setting.shop.shop.order_pdf.invoice_number: "インボイス登録番号" +admin.setting.shop.shop.order_pdf.message: "店舗からのメッセージ" #------------------------------------------------------------------------------------ # 設定:店舗設定:特定商取引法設定 diff --git a/src/Eccube/Resource/template/admin/Setting/Shop/shop_master.twig b/src/Eccube/Resource/template/admin/Setting/Shop/shop_master.twig index 0e37636b8d0..54103beb43a 100644 --- a/src/Eccube/Resource/template/admin/Setting/Shop/shop_master.twig +++ b/src/Eccube/Resource/template/admin/Setting/Shop/shop_master.twig @@ -425,6 +425,22 @@ file that was distributed with this source code. +
+
{{ 'admin.setting.shop.shop.order_pdf'|trans }}
+
+

{{ 'admin.setting.shop.shop.order_pdf.description'|trans }}

+ {% for field in ['shop_name', 'shop_kana', 'shop_name_eng', 'address', 'company_name', 'company_kana', 'phone_number', 'business_hour', 'email', 'invoice_number', 'message'] %} + {% set widget = attribute(form, 'order_pdf_visible_' ~ field) %} +
+
{{ ('admin.setting.shop.shop.order_pdf.' ~ field)|trans }}
+
+ {{ form_widget(widget) }} + {{ form_errors(widget) }} +
+
+ {% endfor %} +
+
diff --git a/src/Eccube/Service/OrderPdfService.php b/src/Eccube/Service/OrderPdfService.php index c3d7b7a4d4b..41c1d6a9c23 100644 --- a/src/Eccube/Service/OrderPdfService.php +++ b/src/Eccube/Service/OrderPdfService.php @@ -285,44 +285,121 @@ protected function renderShopData(): void // 基準座標を設定する $this->setBasePosition(); - // ショップ名 - $this->lfText(125, 58, $this->baseInfoRepository->getShopName(), 8, 'B'); + // user_dataにlogo.pngが配置されている場合は優先的に読み込む + $logoFile = $this->eccubeConfig->get('eccube_html_dir').'/user_data/assets/pdf/logo.png'; - // 郵便番号 - $postalCode = $this->baseInfoRepository->getPostalCode(); - if (!empty($postalCode)) { - $this->lfText(121, 63, "\u{3012}".' '.mb_substr($postalCode, 0, 3).' - '.mb_substr($postalCode, 3, 4), 8); + if (!file_exists($logoFile)) { + $logoFile = $this->eccubeConfig->get('eccube_html_admin_dir').'/assets/pdf/logo.png'; + } + + $this->Image($logoFile, 124, 46, 40); + + // 店舗情報は基準の x=125 から、上から順に「表示トグルが ON かつ 値が非空」の行だけを詰めて描画する。 + // 非表示・空値の行は座標を空けず後続の行が繰り上がる(#6197)。 + // 表示/非表示は基本設定の order_pdf_visible_* トグルで制御する。 + $BaseInfo = $this->baseInfoRepository; + $x = 125; + $y = 58.0; + $lineHeight = 3.3; + + // 店名(太字) + if ($BaseInfo->isOrderPdfVisibleShopName() && !empty($BaseInfo->getShopName())) { + $this->lfText($x, (int) round($y), $BaseInfo->getShopName(), 8, 'B'); + $y += $lineHeight; + } + + // 店名(カナ) + if ($BaseInfo->isOrderPdfVisibleShopKana() && !empty($BaseInfo->getShopKana())) { + $this->lfText($x, (int) round($y), $BaseInfo->getShopKana(), 8); + $y += $lineHeight; + } + + // 店名(英語表記) + if ($BaseInfo->isOrderPdfVisibleShopNameEng() && !empty($BaseInfo->getShopNameEng())) { + $this->lfText($x, (int) round($y), $BaseInfo->getShopNameEng(), 8); + $y += $lineHeight; + } + + // 郵便番号・住所(〒 / 都道府県+addr01 / addr02 の最大3行を1トグルで制御。各行は空ならスキップ) + if ($BaseInfo->isOrderPdfVisibleAddress()) { + $postalCode = $BaseInfo->getPostalCode(); + if (!empty($postalCode)) { + // 郵便マーク(〒)分だけ左に寄せる + $this->lfText($x - 4, (int) round($y), "\u{3012}".' '.mb_substr($postalCode, 0, 3).' - '.mb_substr($postalCode, 3, 4), 8); + $y += $lineHeight; + } + $address1 = $BaseInfo->getPref().$BaseInfo->getAddr01(); + if (!empty($address1)) { + $this->lfText($x, (int) round($y), $address1, 8); + $y += $lineHeight; + } + if (!empty($BaseInfo->getAddr02())) { + $this->lfText($x, (int) round($y), $BaseInfo->getAddr02(), 8); + $y += $lineHeight; + } + } + + // 会社名 + if ($BaseInfo->isOrderPdfVisibleCompanyName() && !empty($BaseInfo->getCompanyName())) { + $this->lfText($x, (int) round($y), $BaseInfo->getCompanyName(), 8); + $y += $lineHeight; } - // 都道府県+所在地 - $text = $this->baseInfoRepository->getPref().$this->baseInfoRepository->getAddr01(); - $this->lfText(125, 66, $text, 8); - $this->lfText(125, 69, $this->baseInfoRepository->getAddr02(), 8); + // 会社名(カナ) + if ($BaseInfo->isOrderPdfVisibleCompanyKana() && !empty($BaseInfo->getCompanyKana())) { + $this->lfText($x, (int) round($y), $BaseInfo->getCompanyKana(), 8); + $y += $lineHeight; + } // 電話番号 - $text = 'TEL: '.$this->baseInfoRepository->getPhoneNumber(); - $this->lfText(125, 72, $text, 8); // TEL・FAX + if ($BaseInfo->isOrderPdfVisiblePhoneNumber() && !empty($BaseInfo->getPhoneNumber())) { + $this->lfText($x, (int) round($y), 'TEL: '.$BaseInfo->getPhoneNumber(), 8); + $y += $lineHeight; + } + + // 店舗営業時間 + if ($BaseInfo->isOrderPdfVisibleBusinessHour() && !empty($BaseInfo->getBusinessHour())) { + $this->lfText($x, (int) round($y), $BaseInfo->getBusinessHour(), 8); + $y += $lineHeight; + } // メールアドレス - if (strlen((string) $this->baseInfoRepository->getEmail01()) > 0) { - $text = 'Email: '.$this->baseInfoRepository->getEmail01(); - $this->lfText(125, 75, $text, 8); // Email + if ($BaseInfo->isOrderPdfVisibleEmail() && strlen((string) $BaseInfo->getEmail01()) > 0) { + $this->lfText($x, (int) round($y), 'Email: '.$BaseInfo->getEmail01(), 8); + $y += $lineHeight; } // インボイス登録番号 - if (!empty($this->baseInfoRepository->getInvoiceRegistrationNumber())) { - $text = '登録番号: '.$this->baseInfoRepository->getInvoiceRegistrationNumber(); - $this->lfText(125, 79, $text, 8); + if ($BaseInfo->isOrderPdfVisibleInvoiceNumber() && !empty($BaseInfo->getInvoiceRegistrationNumber())) { + $this->lfText($x, (int) round($y), '登録番号: '.$BaseInfo->getInvoiceRegistrationNumber(), 8); + $y += $lineHeight; } - // user_dataにlogo.pngが配置されている場合は優先的に読み込む - $logoFile = $this->eccubeConfig->get('eccube_html_dir').'/user_data/assets/pdf/logo.png'; + // 店舗からのメッセージ(長文になり得るため折り返して描画する) + if ($BaseInfo->isOrderPdfVisibleMessage() && !empty($BaseInfo->getMessage())) { + $this->renderShopMessage($x, $y, $BaseInfo->getMessage()); + } + } - if (!file_exists($logoFile)) { - $logoFile = $this->eccubeConfig->get('eccube_html_admin_dir').'/assets/pdf/logo.png'; + /** + * 店舗情報欄に「店舗からのメッセージ」を折り返して描画する. + * + * 合計金額ボックス(y≈95.5)を侵食しないよう、右カラムの残り高さに収める. + */ + protected function renderShopMessage(int $x, float $y, string $message): void + { + // 合計金額ボックス(y≈95.5)の直前までを上限高さとする(侵食させない) + $maxHeight = 94.0 - $y; + if ($maxHeight <= 0) { + return; } - $this->Image($logoFile, 124, 46, 40); + // 後続の描画に影響しないよう、フォント状態を退避・復元する(lfText と同じ扱い) + $this->backupFont(); + $this->SetFont(self::FONT_SJIS, '', 8); + // lfText と同じオフセットで位置を合わせ、幅・最大高さを指定して折り返す + $this->MultiCell(70, 3.3, $message, 0, 'L', false, 1, $x + $this->baseOffsetX, $y + $this->baseOffsetY, true, 0, false, true, $maxHeight, 'T'); + $this->restoreFont(); } /** diff --git a/tests/Eccube/Tests/Form/Type/Admin/ShopMasterTypeTest.php b/tests/Eccube/Tests/Form/Type/Admin/ShopMasterTypeTest.php index 207246dcb9e..7830c3a0f16 100644 --- a/tests/Eccube/Tests/Form/Type/Admin/ShopMasterTypeTest.php +++ b/tests/Eccube/Tests/Form/Type/Admin/ShopMasterTypeTest.php @@ -15,6 +15,7 @@ namespace Eccube\Tests\Form\Type\Admin; +use Eccube\Entity\BaseInfo; use Eccube\Form\Type\Admin\ShopMasterType; use Eccube\Tests\Form\Type\AbstractTypeTestCase; use Symfony\Component\Form\FormInterface; @@ -169,4 +170,28 @@ public function testInValidMessageMaxLength() $this->form->submit($this->formData); $this->assertFalse($this->form->isValid()); } + + /** + * 納品書PDFの出力項目トグルが BaseInfo にマッピングされること (#6197). + * チェックボックスは未チェックをキー欠落で表すため, OFF はキーを送らないことで再現する. + */ + public function testOrderPdfVisibleTogglesMappedToEntity(): void + { + $BaseInfo = new BaseInfo(); + $form = $this->formFactory + ->createBuilder(ShopMasterType::class, $BaseInfo, ['csrf_protection' => false]) + ->getForm(); + + $formData = $this->formData; + // 既定 OFF の店舗営業時間・メッセージを ON にし, 既定 ON の店名はキーを送らず OFF にする + $formData['order_pdf_visible_business_hour'] = '1'; + $formData['order_pdf_visible_message'] = '1'; + + $form->submit($formData); + + $this->assertTrue($form->isValid()); + $this->assertFalse($BaseInfo->isOrderPdfVisibleShopName()); + $this->assertTrue($BaseInfo->isOrderPdfVisibleBusinessHour()); + $this->assertTrue($BaseInfo->isOrderPdfVisibleMessage()); + } } diff --git a/tests/Eccube/Tests/Web/Admin/Order/OrderPdfControllerTest.php b/tests/Eccube/Tests/Web/Admin/Order/OrderPdfControllerTest.php index 7dfa4707ac4..0448eb30e62 100644 --- a/tests/Eccube/Tests/Web/Admin/Order/OrderPdfControllerTest.php +++ b/tests/Eccube/Tests/Web/Admin/Order/OrderPdfControllerTest.php @@ -17,6 +17,7 @@ use Eccube\Common\Constant; use Eccube\Common\EccubeConfig; +use Eccube\Entity\BaseInfo; use Eccube\Entity\Master\OrderStatus; use Eccube\Entity\Order; use Eccube\Entity\OrderPdf; @@ -407,6 +408,50 @@ public function testDownloadWithPreviousInputSuccessWithWeb() $this->assertNull($OrderPdf->getNote3()); } + /** + * 納品書PDFの出力項目トグル(会社名・カナ・営業時間・メッセージ等)を全て ON にした状態でも + * PDF が生成できること (#6197). とくに「店舗からのメッセージ」の折り返し描画(MultiCell)の回帰防止. + */ + public function testDownloadSuccessWithAllOrderPdfItemsVisible() + { + $BaseInfo = $this->entityManager->getRepository(BaseInfo::class)->find(1); + $this->assertInstanceOf(BaseInfo::class, $BaseInfo); + $BaseInfo->setOrderPdfVisibleShopName(true) + ->setOrderPdfVisibleShopKana(true) + ->setOrderPdfVisibleShopNameEng(true) + ->setOrderPdfVisibleAddress(true) + ->setOrderPdfVisibleCompanyName(true) + ->setOrderPdfVisibleCompanyKana(true) + ->setOrderPdfVisiblePhoneNumber(true) + ->setOrderPdfVisibleBusinessHour(true) + ->setOrderPdfVisibleEmail(true) + ->setOrderPdfVisibleInvoiceNumber(true) + ->setOrderPdfVisibleMessage(true) + ->setCompanyName('テスト株式会社') + ->setCompanyKana('テストカブシキガイシャ') + ->setBusinessHour('10:00-19:00') + ->setMessage(str_repeat('毎度ご利用ありがとうございます。', 20)); + $this->entityManager->flush(); + + $Order = $this->createOrderForSearch(); + $Shippings = $Order->getShippings(); + $shippingId = $Shippings[0]->getId(); + + $client = $this->client; + $crawler = $client->request(Request::METHOD_POST, $this->generateUrl('admin_order_export_pdf'), + [ + '_token' => 'dummy', + 'ids' => [$shippingId], + ]); + + $form = $this->getForm($crawler); + $client->submit($form); + + $this->actual = $client->getResponse()->headers->get('Content-Type'); + $this->expected = 'application/pdf'; + $this->verify(); + } + private function getForm(Crawler $crawler): Form { $form = $crawler->selectButton('作成')->form(); diff --git a/tests/Eccube/Tests/Web/Admin/Setting/Shop/ShopControllerTest.php b/tests/Eccube/Tests/Web/Admin/Setting/Shop/ShopControllerTest.php index f422fc34b69..9dec4f18bcd 100644 --- a/tests/Eccube/Tests/Web/Admin/Setting/Shop/ShopControllerTest.php +++ b/tests/Eccube/Tests/Web/Admin/Setting/Shop/ShopControllerTest.php @@ -133,6 +133,43 @@ public static function dataSanitizeCsvFormulasProvider(): \Iterator yield [false, false]; } + /** + * 納品書PDFの出力項目トグルが BaseInfo に保存されること (#6197). + * 既定 ON の項目を OFF に、既定 OFF の項目を ON にできることの双方を確認する. + * チェックボックスは未チェックをキー欠落で表すため, 無効化はキーを送らないことで再現する. + */ + #[DataProvider(methodName: 'dataOrderPdfVisibleProvider')] + #[Group(name: 'cache-clear')] + public function testSubmitPersistsOrderPdfVisibleOptions(bool $checked, bool $expected): void + { + $formData = $this->createFormData(); + foreach (['order_pdf_visible_shop_name', 'order_pdf_visible_message'] as $key) { + if ($checked) { + $formData[$key] = '1'; + } else { + unset($formData[$key]); + } + } + $this->client->request( + Request::METHOD_POST, + $this->generateUrl('admin_setting_shop'), + ['shop_master' => $formData] + ); + + $this->entityManager->clear(); + $BaseInfo = $this->entityManager->getRepository(BaseInfo::class)->find(1); + $this->assertInstanceOf(BaseInfo::class, $BaseInfo); + // 既定 ON の店名, 既定 OFF のメッセージ, いずれも送信内容どおりに保存される + $this->assertSame($expected, $BaseInfo->isOrderPdfVisibleShopName()); + $this->assertSame($expected, $BaseInfo->isOrderPdfVisibleMessage()); + } + + public static function dataOrderPdfVisibleProvider(): \Iterator + { + yield [true, true]; + yield [false, false]; + } + public static function dataSubmitProvider(): \Iterator { yield [false, false];