feat(webshop): add print & pixel size support (feature 043)#4388
Conversation
Co-authored-by: ildyria <627094+ildyria@users.noreply.github.com>
…for webshop print & pixel sizes Co-authored-by: ildyria <627094+ildyria@users.noreply.github.com>
Co-authored-by: ildyria <627094+ildyria@users.noreply.github.com>
Co-authored-by: ildyria <627094+ildyria@users.noreply.github.com>
…h comment Co-authored-by: ildyria <627094+ildyria@users.noreply.github.com>
…pixel sizes Co-authored-by: ildyria <627094+ildyria@users.noreply.github.com>
Codecov Report❌ Patch coverage is 🚀 New features to boost your workflow:
|
|
@coderabbitai do your thing. |
|
✅ Action performedReview finished.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
💤 Files with no reviewable changes (1)
🚧 Files skipped from review as they are similar to previous changes (6)
📝 WalkthroughWalkthroughAdds global print and pixel size catalogues, per-purchasable size pricing, basket/order support for print/pixel items, admin CRUD and frontend UI, translations, docs, migrations, and tests. ChangesWebshop print and pixel sizes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
|
There was a problem hiding this comment.
Actionable comments posted: 17
🧹 Nitpick comments (23)
lang/es/left-menu.php (1)
27-27: ⚡ Quick winEnglish placeholder should be translated to Spanish.
The
shopSizeskey currently uses the English text "Size Catalogue" instead of a Spanish translation (e.g., "Catálogo de Tamaños"). While English placeholders are acceptable for the initial rollout, consider translating to Spanish in a follow-up.lang/es/webshop.php (1)
36-38: ⚡ Quick winComplete Spanish translations for remaining English placeholders.
Several keys have Spanish translations (lines 81, 84-85), but many remain in English (basketList additions, buyMeDialog, sizeCatalogue, printSizePricesInput, pixelSizePricesInput, shippingAddress sections). Consider completing the Spanish translations in a follow-up for consistency.
Also applies to: 235-279
lang/fa/left-menu.php (1)
28-28: ⚡ Quick winEnglish placeholder should be translated to Persian.
The
shopSizeskey uses English text instead of Persian (Farsi) translation. Consider translating to Persian in a follow-up for consistency with the locale.lang/fa/webshop.php (1)
36-38: ⚡ Quick winComplete Persian translations for remaining English placeholders.
Several keys have Persian translations (lines 81, 84-85), but many remain in English (basketList additions, buyMeDialog, sizeCatalogue, printSizePricesInput, pixelSizePricesInput, shippingAddress sections). Consider completing the Persian translations in a follow-up for consistency.
Also applies to: 235-279
lang/fr/left-menu.php (1)
28-28: ⚡ Quick winEnglish placeholder should be translated to French.
The
shopSizeskey uses English text instead of a French translation (e.g., "Catalogue de Tailles"). Consider translating to French in a follow-up for consistency with the locale.lang/fr/webshop.php (1)
36-38: ⚡ Quick winComplete French translations for remaining English placeholders.
Several keys have French translations (lines 81, 84-85), but many remain in English (basketList additions, buyMeDialog, sizeCatalogue, printSizePricesInput, pixelSizePricesInput, shippingAddress sections). Consider completing the French translations in a follow-up for consistency.
Also applies to: 235-279
lang/hu/left-menu.php (1)
28-28: ⚡ Quick winEnglish placeholder should be translated to Hungarian.
The
shopSizeskey uses English text instead of a Hungarian translation (e.g., "Méret Katalógus"). Consider translating to Hungarian in a follow-up for consistency with the locale.lang/no/webshop.php (1)
235-279: ⚡ Quick winComplete Norwegian translations for new webshop sections.
The new translation groups (
buyMeDialog,sizeCatalogue,printSizePricesInput,pixelSizePricesInput,shippingAddress) contain English placeholder strings rather than Norwegian translations. While lines 81, 84-85 correctly provide Norwegian text, these larger sections remain untranslated.For a Norwegian locale file, users expect Norwegian strings throughout.
lang/pl/left-menu.php (1)
28-28: ⚡ Quick winTranslate 'Size Catalogue' to Polish.
The value for
shopSizesis in English. Polish locale files should provide Polish translations.lang/pl/webshop.php (1)
235-279: ⚡ Quick winComplete Polish translations for new webshop sections.
The new translation groups (
buyMeDialog,sizeCatalogue,printSizePricesInput,pixelSizePricesInput,shippingAddress) contain English placeholder strings. Lines 81, 84-85 correctly provide Polish text, but these sections remain untranslated.lang/pt/left-menu.php (1)
28-28: ⚡ Quick winTranslate 'Size Catalogue' to Portuguese.
The value for
shopSizesis in English. Portuguese locale files should provide Portuguese translations.lang/pt/webshop.php (1)
235-279: ⚡ Quick winComplete Portuguese translations for new webshop sections.
The new translation groups (
buyMeDialog,sizeCatalogue,printSizePricesInput,pixelSizePricesInput,shippingAddress) contain English placeholder strings. Lines 81, 84-85 correctly provide Portuguese text, but these sections remain untranslated.lang/ru/left-menu.php (1)
28-28: ⚡ Quick winTranslate 'Size Catalogue' to Russian.
The value for
shopSizesis in English. Russian locale files should provide Russian translations.lang/ru/webshop.php (1)
235-279: ⚡ Quick winComplete Russian translations for new webshop sections.
The new translation groups (
buyMeDialog,sizeCatalogue,printSizePricesInput,pixelSizePricesInput,shippingAddress) contain English placeholder strings. Lines 81, 84-85 correctly provide Russian text, but these sections remain untranslated.lang/sk/left-menu.php (1)
28-28: ⚡ Quick winTranslate 'Size Catalogue' to Slovak.
The value for
shopSizesis in English. Slovak locale files should provide Slovak translations.app/Http/Controllers/Admin/PrintSizeManagementController.php (1)
31-31: ⚡ Quick winRemove redundant
->all()call.
PrintSize::all()already returns aCollection. The chained->all()converts it to a plain array, but thenPrintSizeResource::collect()expects a collection or array. The double->all()is redundant; usePrintSize::all()directly.♻️ Proposed fix
- return PrintSizeResource::collect(PrintSize::all()->all()); + return PrintSizeResource::collect(PrintSize::all());app/Http/Controllers/Shop/CheckoutController.php (1)
70-88: ⚖️ Poor tradeoffConsider refactoring repetitive null checks.
The six shipping address fields follow an identical pattern. While explicit and clear, this could be refactored to reduce duplication.
♻️ Optional refactor to reduce duplication
- // Store shipping address if provided - if ($request->shipping_street_name !== null) { - $order->shipping_street_name = $request->shipping_street_name; - } - if ($request->shipping_street_number !== null) { - $order->shipping_street_number = $request->shipping_street_number; - } - if ($request->shipping_additional_info !== null) { - $order->shipping_additional_info = $request->shipping_additional_info; - } - if ($request->shipping_city !== null) { - $order->shipping_city = $request->shipping_city; - } - if ($request->shipping_post_code !== null) { - $order->shipping_post_code = $request->shipping_post_code; - } - if ($request->shipping_country !== null) { - $order->shipping_country = $request->shipping_country; - } + // Store shipping address if provided + foreach (['shipping_street_name', 'shipping_street_number', 'shipping_additional_info', + 'shipping_city', 'shipping_post_code', 'shipping_country'] as $field) { + if ($request->$field !== null) { + $order->$field = $request->$field; + } + }database/migrations/2026_05_31_000006_extend_orders_for_shipping.php (1)
20-25: ⚖️ Poor tradeoffConsider specifying explicit lengths for shipping address fields.
While Laravel's default
varchar(255)works, explicit lengths would better express intent:
shipping_post_code: typically 10-20 chars maxshipping_country: could use 2 chars for ISO codes or 100 for full names- Street/city fields: 255 is reasonable
♻️ Optional refinement with explicit lengths
Schema::table('orders', function (Blueprint $table) { - $table->string('shipping_street_name')->nullable()->after('comment'); - $table->string('shipping_street_number')->nullable()->after('shipping_street_name'); + $table->string('shipping_street_name', 255)->nullable()->after('comment'); + $table->string('shipping_street_number', 50)->nullable()->after('shipping_street_name'); $table->string('shipping_additional_info')->nullable()->after('shipping_street_number'); - $table->string('shipping_city')->nullable()->after('shipping_additional_info'); - $table->string('shipping_post_code')->nullable()->after('shipping_city'); - $table->string('shipping_country')->nullable()->after('shipping_post_code'); + $table->string('shipping_city', 255)->nullable()->after('shipping_additional_info'); + $table->string('shipping_post_code', 20)->nullable()->after('shipping_city'); + $table->string('shipping_country', 100)->nullable()->after('shipping_post_code'); });resources/js/components/forms/shop-management/PixelSizePricesInput.vue (2)
43-48: ⚡ Quick winConsolidate Vue imports to eliminate duplication.
The
watchfunction is imported separately on line 48, but it should be imported alongsideonMountedandrefon line 43.♻️ Consolidate imports
-import { onMounted, ref } from "vue"; +import { onMounted, ref, watch } from "vue"; import InputCurrency from "`@/components/forms/basic/InputCurrency.vue`"; import ShopManagementService from "`@/services/shop-management-service`"; import { useShopManagementStore } from "`@/stores/ShopManagement`"; import { storeToRefs } from "pinia"; -import { watch } from "vue";
65-65: ⚡ Quick winCompute default item dynamically to avoid stale pricing.
The
_defaultItemconstant capturesdefault_price_cents.valueonce at component definition. If the store'sdefault_price_centschanges after the component mounts, newly added rows will still use the stale initial value.♻️ Compute default item dynamically
-const _defaultItem: PixelSizeAssignment = { pixel_size_id: 0, price: default_price_cents.value, license_type: "personal" }; +function createDefaultItem(): PixelSizeAssignment { + return { pixel_size_id: 0, price: default_price_cents.value, license_type: "personal" }; +}Then update line 34:
- `@click`="items.push({ ..._defaultItem })" + `@click`="items.push(createDefaultItem())"resources/js/components/forms/shop-management/PrintSizePricesInput.vue (2)
40-44: ⚡ Quick winConsolidate Vue imports to eliminate duplication.
The
watchfunction is imported separately on line 74, but it should be imported alongsideonMountedandrefon line 40.♻️ Consolidate imports
-import { onMounted, ref } from "vue"; +import { onMounted, ref, watch } from "vue"; import InputCurrency from "`@/components/forms/basic/InputCurrency.vue`"; import ShopManagementService from "`@/services/shop-management-service`"; import { useShopManagementStore } from "`@/stores/ShopManagement`"; import { storeToRefs } from "pinia"; // ... later in file -import { watch } from "vue"; watch(Also applies to: 74-74
60-60: ⚡ Quick winCompute default item dynamically to avoid stale pricing.
The
_defaultItemconstant capturesdefault_price_cents.valueonce at component definition. If the store'sdefault_price_centschanges after the component mounts, newly added rows will still use the stale initial value.♻️ Compute default item dynamically
-const _defaultItem: PrintSizeAssignment = { print_size_id: 0, price: default_price_cents.value }; +function createDefaultItem(): PrintSizeAssignment { + return { print_size_id: 0, price: default_price_cents.value }; +}Then update line 31:
- `@click`="items.push({ ..._defaultItem })" + `@click`="items.push(createDefaultItem())"tests/Unit/Actions/Shop/PurchasableSyncSizesTest.php (1)
196-196: ⚡ Quick winPrefer strict identity assertions for ID checks.
Use
assertSame()instead ofassertEquals()for IDs to keep strict-comparison semantics in tests.As per coding guidelines, PHP code should "Use strict comparison (===) instead of loose comparison (==)".
Also applies to: 203-203
Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 833a0a99-a478-4e8f-8318-28f171a35bb5
📒 Files selected for processing (146)
Dockerfileapp/Actions/Shop/BasketService.phpapp/Actions/Shop/OrderService.phpapp/Actions/Shop/PurchasableService.phpapp/Contracts/Http/Requests/RequestAttribute.phpapp/DTO/PixelSizeAssignment.phpapp/DTO/PrintSizeAssignment.phpapp/Enum/PurchasableLicenseType.phpapp/Http/Controllers/Admin/PixelSizeManagementController.phpapp/Http/Controllers/Admin/PrintSizeManagementController.phpapp/Http/Controllers/Admin/ShopManagementController.phpapp/Http/Controllers/Shop/BasketController.phpapp/Http/Controllers/Shop/CatalogueSizesController.phpapp/Http/Controllers/Shop/CheckoutController.phpapp/Http/Requests/Basket/AddPixelItemRequest.phpapp/Http/Requests/Basket/AddPrintItemRequest.phpapp/Http/Requests/Catalog/GetCatalogueSizesRequest.phpapp/Http/Requests/Checkout/CreateSessionRequest.phpapp/Http/Requests/ShopManagement/PixelSize/CreatePixelSizeRequest.phpapp/Http/Requests/ShopManagement/PixelSize/DeletePixelSizeRequest.phpapp/Http/Requests/ShopManagement/PixelSize/UpdatePixelSizeRequest.phpapp/Http/Requests/ShopManagement/PrintSize/CreatePrintSizeRequest.phpapp/Http/Requests/ShopManagement/PrintSize/DeletePrintSizeRequest.phpapp/Http/Requests/ShopManagement/PrintSize/UpdatePrintSizeRequest.phpapp/Http/Requests/ShopManagement/PurchasableAlbumRequest.phpapp/Http/Requests/ShopManagement/PurchasablePhotoRequest.phpapp/Http/Requests/ShopManagement/UpdatePurchasablePriceRequest.phpapp/Http/Resources/Shop/CatalogueSizesResource.phpapp/Http/Resources/Shop/EditablePurchasableResource.phpapp/Http/Resources/Shop/OrderItemResource.phpapp/Http/Resources/Shop/OrderResource.phpapp/Http/Resources/Shop/PixelSizeResource.phpapp/Http/Resources/Shop/PrintSizeResource.phpapp/Http/Resources/Shop/PurchasablePixelSizeResource.phpapp/Http/Resources/Shop/PurchasablePrintSizeResource.phpapp/Listeners/OrderCompletedListener.phpapp/Metadata/Cache/RouteCacheManager.phpapp/Models/Order.phpapp/Models/OrderItem.phpapp/Models/PixelSize.phpapp/Models/PrintSize.phpapp/Models/Purchasable.phpapp/Models/PurchasablePixelSize.phpapp/Models/PurchasablePrintSize.phpdatabase/factories/PixelSizeFactory.phpdatabase/factories/PrintSizeFactory.phpdatabase/factories/PurchasablePixelSizeFactory.phpdatabase/factories/PurchasablePrintSizeFactory.phpdatabase/migrations/2026_05_31_000001_create_print_sizes_table.phpdatabase/migrations/2026_05_31_000002_create_pixel_sizes_table.phpdatabase/migrations/2026_05_31_000003_create_purchasable_print_sizes_table.phpdatabase/migrations/2026_05_31_000004_create_purchasable_pixel_sizes_table.phpdatabase/migrations/2026_05_31_000005_extend_order_items_for_print.phpdatabase/migrations/2026_05_31_000006_extend_orders_for_shipping.phpdocs/specs/4-architecture/features/043-webshop-print-pixel-sizes/plan.mddocs/specs/4-architecture/features/043-webshop-print-pixel-sizes/spec.mddocs/specs/4-architecture/features/043-webshop-print-pixel-sizes/tasks.mddocs/specs/4-architecture/open-questions.mddocs/specs/4-architecture/roadmap.mdlang/ar/left-menu.phplang/ar/webshop.phplang/bg/left-menu.phplang/bg/webshop.phplang/cz/left-menu.phplang/cz/webshop.phplang/de/left-menu.phplang/de/webshop.phplang/el/left-menu.phplang/el/webshop.phplang/en/left-menu.phplang/en/webshop.phplang/es/left-menu.phplang/es/webshop.phplang/fa/left-menu.phplang/fa/webshop.phplang/fr/left-menu.phplang/fr/webshop.phplang/hu/left-menu.phplang/hu/webshop.phplang/it/left-menu.phplang/it/webshop.phplang/ja/left-menu.phplang/ja/webshop.phplang/nl/left-menu.phplang/nl/webshop.phplang/no/left-menu.phplang/no/webshop.phplang/pl/left-menu.phplang/pl/webshop.phplang/pt/left-menu.phplang/pt/webshop.phplang/ru/left-menu.phplang/ru/webshop.phplang/sk/left-menu.phplang/sk/webshop.phplang/sv/left-menu.phplang/sv/webshop.phplang/tr/left-menu.phplang/tr/webshop.phplang/vi/left-menu.phplang/vi/webshop.phplang/zh_CN/left-menu.phplang/zh_CN/webshop.phplang/zh_TW/left-menu.phplang/zh_TW/webshop.phpphpstan.neonresources/js/components/forms/album/AlbumPurchasable.vueresources/js/components/forms/gallery-dialogs/BuyMeDialog.vueresources/js/components/forms/shop-management/PixelSizeFormDialog.vueresources/js/components/forms/shop-management/PixelSizePricesInput.vueresources/js/components/forms/shop-management/PrintSizeFormDialog.vueresources/js/components/forms/shop-management/PrintSizePricesInput.vueresources/js/components/forms/shop-management/SizeDeleteDialog.vueresources/js/components/webshop/InfoSection.vueresources/js/components/webshop/OrderSummary.vueresources/js/composables/album/buyMeActions.tsresources/js/composables/checkout/useStepOne.tsresources/js/composables/checkout/useStepTwo.tsresources/js/composables/contextMenus/leftMenu.tsresources/js/composables/useAdminTiles.tsresources/js/lychee.d.tsresources/js/router/routes.tsresources/js/services/shop-management-service.tsresources/js/services/webshop-service.tsresources/js/stores/OrderManagement.tsresources/js/views/admin/shop/PrintPixelSizesAdmin.vueresources/js/views/webshop/BasketList.vueresources/js/views/webshop/CheckoutPage.vueresources/js/views/webshop/OrderDownload.vueresources/sass/app.cssroutes/api_v2_shop.phproutes/web_v2.phptests/Unit/Actions/Db/OptimizeDbTest.phptests/Unit/Actions/Db/OptimizeTablesTest.phptests/Unit/Actions/Shop/CanProcessPaymentPrintTest.phptests/Unit/Actions/Shop/OrderServiceAddSizeItemsTest.phptests/Unit/Actions/Shop/PurchasableSyncSizesTest.phptests/Unit/Enum/PurchasableLicenseTypeTest.phptests/Webshop/BasketPrintPixelItemsTest.phptests/Webshop/Checkout/CheckoutShippingAddressTest.phptests/Webshop/OrderManagement/OrderShippingAddressDisplayTest.phptests/Webshop/Purchasables/CatalogueSizesControllerTest.phptests/Webshop/Purchasables/PixelSizeManagementControllerTest.phptests/Webshop/Purchasables/PrintSizeManagementControllerTest.phptests/Webshop/Purchasables/PurchasablePriceWithSizesTest.phptests/Webshop/Purchasables/ShopManagementControllerTest.php
|
@coderabbitai time for another review, fixed your findings. |
|
✅ Action performedReview finished.
|
Implements feature 043: webshop support for physical print sizes and pixel-dimension downloads, including per-purchasable price assignments, admin catalogue management, basket/checkout flow with shipping address, and full i18n coverage.
Backend
PurchasablePrintSizeandPurchasablePixelSizenow declareprotected $with = ['printSize']/protected $with = ['pixelSize']to preventLazyLoadingViolationException(enforced globally viaModel::shouldBeStrict())lychee.d.ts:PrintSizeResource,PixelSizeResource,PurchasablePrintSizeResource,PurchasablePixelSizeResource,CatalogueSizesResource; addedis_print: booleantoOrderItemResourceTests
CanProcessPaymentPrintTest— verifiesOrder::canProcessPayment()returnsfalsewhen a print item is present but shipping address fields are missing (city, post code, country)PurchasableLicenseTypeTest— verifies thePRINTenum case exists and is distinctFrontend services
shop-management-service.ts: CRUD methods for global print/pixel size catalogue (listPrintSizes,createPrintSize,updatePrintSize,deletePrintSize, and pixel equivalents);updatePurchasablePricesfor per-purchasable assignmentswebshop-service.ts:getCatalogueSizes,addPrintPhotoToBasket,addPixelPhotoToBasket; shipping fields onCreateCheckoutOrderManagementstore:addPrintPhoto,addPixelPhotoactions;hasPrintItemsgetterFrontend UI
PrintPixelSizesAdmin.vue: Admin DataTable page at/admin/shop/sizesfor managing the global size catalogue (create/edit/delete for both print and pixel sizes)BuyMeDialog.vue/buyMeActions.ts: Tabbed item-type selector (Digital / Print / Pixel); fetches available sizes viagetCatalogueSizesAlbumPurchasable.vue: IntegratesPrintSizePricesInputandPixelSizePricesInputcomponents; "Update" now callsupdatePurchasablePricesInfoSection.vue(shown whenhasPrintItems); shipping refs collected inuseStepOne, passed throughuseStepTwotocreateSessioni18n
New translation groups added to
lang/en/webshop.php(buyMeDialog,sizeCatalogue,printSizePricesInput,pixelSizePricesInput,shippingAddress) andlang/en/left-menu.php(shopSizes), with English-placeholder entries propagated to all 22 non-English locales.Summary by CodeRabbit
New Features
Documentation
Tests