Compare commits

...
Sign in to create a new pull request.

483 commits

Author SHA1 Message Date
Jérémy Munsch
6a6628ec70
Fixes #74 slide create 2023-08-08 17:18:36 +02:00
Vincent BRIDIER
fcb9a15bd5 Merge branch 'update-mairet' into 'nettoyage'
Update mairet

See merge request ldc-web/projets/boutique-cnc!11
2023-07-12 10:30:31 +00:00
Vincent BRIDIER
653bdead7d Merge branch 'fix-logo-size' into 'update-mairet'
Fixed logo size

See merge request ldc-web/projets/boutique-cnc!14
2023-07-12 10:29:46 +00:00
Noah Lancien
600efe894c Fixed logo size 2023-07-11 16:57:29 +02:00
Vincent BRIDIER
14b2c8c2c7 Merge branch 'manage-tag-product-cache' into 'update-mairet'
Refresh Tag cache when updating a product

See merge request ldc-web/projets/boutique-cnc!13
2023-07-11 14:23:36 +00:00
Noah Lancien
dff97f2501 Refresh Tag cache when updating a product 2023-07-11 16:18:25 +02:00
Vincent BRIDIER
027edccd7a Merge branch 'lazy-load-discounts' into 'update-mairet'
Lazy load discounts

See merge request ldc-web/projets/boutique-cnc!12
2023-07-11 09:02:37 +00:00
Noah Lancien
ca78be22fd 'with discount' is back 2023-07-11 11:01:31 +02:00
Noah Lancien
7cf5db11a6 Reverted boolean changes for status 2023-07-11 10:59:00 +02:00
Noah Lancien
a0f8a47683 The good way to lazy load discounts 2023-07-11 10:55:04 +02:00
Vincent BRIDIER
e98ad69729 Merge branch 'share-shop-to-contact-form' into 'update-mairet'
Shared first shop to contact form

See merge request ldc-web/projets/boutique-cnc!10
2023-07-10 13:32:10 +00:00
noah.lancien
707da5e9e9 Shared first shop to contact form 2023-07-10 13:32:09 +00:00
Noah Lancien
bc2302ef7a Replaced first by firstOrFail to get the first shop 2023-07-10 15:26:40 +02:00
Noah Lancien
9f8196a175 Shared the first shop registered to the contact view 2023-07-10 15:14:31 +02:00
Alexis Gousseau
0ad6161276 add favicon to the compilation 2023-07-07 17:56:39 +02:00
Jérémy Munsch
63d153b3d3
add message on canceled order 2023-05-26 09:10:17 +02:00
Jérémy Munsch
74a51852c8
Fix transaction mutate status 2023-05-26 09:07:34 +02:00
Jérémy Munsch
bf2b05dca4
display order fix 2023-05-26 08:44:11 +02:00
Jérémy Munsch
92b9929849
Tons of fixes 2023-05-25 20:04:46 +02:00
Jérémy Munsch
ffe65f6ec6
Fix model Order 2023-05-25 16:36:16 +02:00
Jérémy Munsch
ee4a4aa729
Fix system pay payment adapter 2023-05-25 16:32:16 +02:00
Jérémy Munsch
3d79f8bde3
refix transaction 2023-05-25 16:02:35 +02:00
Jérémy Munsch
fd10784602
Fix refund id 2023-05-25 15:59:10 +02:00
Jérémy Munsch
5fa3a40871
Fix systempay typo on listeners 2023-05-25 15:38:18 +02:00
Jérémy Munsch
dafb2fe840
update readme 2023-05-25 13:54:46 +02:00
Jérémy Munsch
eed05cb64a
update readme 2023-05-25 13:49:33 +02:00
Jérémy Munsch
2f27550ef4
Refactor + fix get refund items 2023-05-25 13:45:30 +02:00
Jérémy Munsch
81d82640f1
readd @can on bo order form 2023-05-25 11:47:45 +02:00
Jérémy Munsch
9ebd895b6f
update theeme 2023-05-25 11:25:21 +02:00
Jérémy Munsch
bcd4341539
Fix complete refund from systempay bo 2023-05-25 11:24:51 +02:00
Jérémy Munsch
515a265ea0
Better order amounts 2023-05-25 11:11:17 +02:00
Jérémy Munsch
90caf6e30d
Remove order 'can' on order form 2023-05-25 10:40:54 +02:00
Jérémy Munsch
05da2e261f
Fix for final order invoice download fi no transaction used for payment can be found 2023-05-25 09:56:39 +02:00
Jérémy Munsch
ca5d343138
Fix fo static method usage 2023-05-25 07:50:26 +02:00
Jérémy Munsch
d3cb440627
SYNC 160 static pages booleans display 2023-05-24 18:13:58 +02:00
Jérémy Munsch
d52bc07c71
Fix for order display 2023-05-24 17:59:50 +02:00
Jérémy Munsch
916620bdb6
Upgrade getTransactionUsedForPayment 2023-05-24 17:54:15 +02:00
Jérémy Munsch
4c66ce1903
Order bo form add warning message 2023-05-24 17:53:58 +02:00
Jérémy Munsch
2edd1bc5b4
fix DeleteOldTimeslots 2023-05-24 17:38:03 +02:00
Jérémy Munsch
a0ed503b79
fix UpdateTimeslotRequest::parseTime 2023-05-24 17:12:31 +02:00
Jérémy Munsch
6e7205ffac
Order details replace NC -> 0 2023-05-24 17:10:23 +02:00
Jérémy Munsch
6c6e273b7a
fucking fix on taxes 2023-05-23 19:08:52 +02:00
Jérémy Munsch
73087d35d5
Hope fixed arrondis de merde 2023-05-23 18:20:44 +02:00
Jérémy Munsch
60c5a5c568
add refund taxes as neg values 2023-05-23 17:53:01 +02:00
Jérémy Munsch
42a394df31
Add items qty hints on bo orders 2023-05-23 17:36:51 +02:00
Jérémy Munsch
5da192d452
Fix invoice TVA display 2023-05-23 16:18:37 +02:00
Jérémy Munsch
5b6f4b1a40
refactore UpdateTimeslotRequest 2023-05-23 14:17:14 +02:00
Jérémy Munsch
9cb936965c
Fix arrondis de mort 2023-05-23 14:14:23 +02:00
Jérémy Munsch
23ed442eb7
fix on Order getAllTransactionsItems 2023-05-23 14:03:25 +02:00
Jérémy Munsch
a54f2656e0
fix getRemainingUnrefundedItems to filter shipped items 2023-05-23 13:59:39 +02:00
Jérémy Munsch
6cfc3e381e
Repass on policies 2023-05-23 13:42:43 +02:00
Jérémy Munsch
0055add48f
fix basket cart calculation on taxes 2023-05-23 12:39:50 +02:00
Jérémy Munsch
f13f4c55af
Fix order invoice generation 2023-05-23 11:27:20 +02:00
Jérémy Munsch
512274a11f
Fix refund with no items available 2023-05-23 10:43:25 +02:00
Jérémy Munsch
69c09b8e25
Fixed getRemainingUnrefundedItems take into account shipped items 2023-05-23 10:37:17 +02:00
Jérémy Munsch
16d01d96b9
Fixed Prevent download final invoice if there are transaction to be resolved 2023-05-23 10:27:27 +02:00
Jérémy Munsch
e42fdc4c15
Fix tax calculation for order refund 2023-05-23 10:17:42 +02:00
Jérémy Munsch
c1ac3d3a20
fix order complete refund 2023-05-23 09:48:55 +02:00
Jérémy Munsch
2bc6d64904
SYNC 150 Add final invoice feature 2023-05-23 09:32:54 +02:00
Jérémy Munsch
bea7e22cbb
final fix ok forn invoices 2023-05-19 19:49:41 +02:00
Jérémy Munsch
99a8a0da47
Debug add systempay sync button 2023-05-19 16:31:51 +02:00
Jérémy Munsch
5af3ffa261
display fixes on bo order form 2023-05-19 16:23:06 +02:00
Jérémy Munsch
622f75c298
Order bo form fix 2023-05-19 15:50:44 +02:00
Jérémy Munsch
68c8705dd3
fix Order::getRemainingUnrefundedItems 2023-05-19 15:44:50 +02:00
Jérémy Munsch
9a0f882025
Delivery status update 2023-05-19 15:17:33 +02:00
Jérémy Munsch
770bf8e825
Fix order resolve calculation 2023-05-19 12:54:41 +02:00
Jérémy Munsch
94fdd964cb
fix misupdate file 2023-05-19 12:43:52 +02:00
Jérémy Munsch
b2ab49ca58
Delete oldimeslot fix 2023-05-19 12:41:45 +02:00
Jérémy Munsch
f17f697de1
fix empty mail on resolve 2023-05-19 12:40:09 +02:00
Jérémy Munsch
038d9911c0
inshopfix 2023-05-19 12:23:15 +02:00
Jérémy Munsch
8e39d417e2
Fixes and debug from System Env re7Btq 2023-05-19 12:11:38 +02:00
Jérémy Munsch
5e4af3e91e
destroyCurrentBasket force delete unused timeslot 2023-05-17 11:41:33 +02:00
Jérémy Munsch
fa18aef1a4
Undo timeslot prunable 2023-05-17 11:41:07 +02:00
Jérémy Munsch
105907413c
update destroy current basket 2023-05-17 11:16:55 +02:00
Jérémy Munsch
776f3344c6
fix theme typo 2023-05-17 10:32:33 +02:00
Jérémy Munsch
aec728ff19
add order button on swiper 2023-05-17 10:18:02 +02:00
Jérémy Munsch
397738ef90
Inshop Transaction ok 2023-05-17 09:49:11 +02:00
Jérémy Munsch
0d31003dd3
Fix order tunnel associating timeslot 2023-05-17 08:40:12 +02:00
Jérémy Munsch
a4c937aac6
Timeslot selection fix 2023-05-16 20:52:19 +02:00
Jérémy Munsch
75c9eefa39
Timeslot select enforce, allow reuse the selected one 2023-05-16 20:34:47 +02:00
Jérémy Munsch
52b38fdc53
Timeslotpicker Disabled change while loading 2023-05-16 19:57:27 +02:00
Jérémy Munsch
ff0f58afe2
Fix libération de timeslot 2023-05-16 18:41:30 +02:00
Jérémy Munsch
f7639af7d8
Add js click warning on boolean changes 2023-05-16 18:13:38 +02:00
Jérémy Munsch
00d9a61806
Timeslot fix 2023-05-16 16:52:10 +02:00
Jérémy Munsch
ad6df7a926
Fix recurrent Orderprepare Ts compile bug 2023-05-16 13:49:36 +02:00
Jérémy Munsch
d1095dd4d2
Fix timeslot mecanism 2023-05-16 12:30:02 +02:00
Jérémy Munsch
f318750b1d
multiple fixes avec sebastien 2023-05-16 11:32:11 +02:00
Jérémy Munsch
41d379fb13
fix delivery form old 2023-05-16 09:54:49 +02:00
Jérémy Munsch
1f38e2380c
fix typo 2023-05-16 09:01:38 +02:00
Jérémy Munsch
4fc504bf9a
fixes inShop 2023-05-16 08:57:35 +02:00
Jérémy Munsch
5e5493ff82
More Fixes 2023-05-16 08:33:17 +02:00
Jérémy Munsch
cbc8f85cc0
Fix WeightUnit:: convertWeightToAdequatUnit to static 2023-05-16 08:17:45 +02:00
Jérémy Munsch
6472f29fdf
More Fixes 2023-05-16 08:13:10 +02:00
Jérémy Munsch
ae047d5b21
Multiple fixes from Seb file Part 1 2023-05-16 07:43:17 +02:00
Jérémy Munsch
c0316d2f09
phpcs fix 2023-05-16 07:16:38 +02:00
Jérémy Munsch
33d530093e
phpcs fix 2023-05-15 12:40:51 +02:00
Jérémy Munsch
51163f325a
Fix Order model getTotalPayed 2023-05-15 12:22:40 +02:00
Jérémy Munsch
d771f14cce
Fix SystempayCreditTransactionUpdate 2023-05-15 11:47:48 +02:00
Jérémy Munsch
9f58c867e1
Fix Bo order prepare 2023-05-15 10:21:34 +02:00
Jérémy Munsch
d07f72513b
Shop model phpcs fix 2023-05-12 12:23:46 +02:00
Jérémy Munsch
ac20e3d0b8
update theme 2023-05-12 11:59:16 +02:00
Jérémy Munsch
c3f227fbf3
ORderPReparefix 2023-05-12 11:41:49 +02:00
Jérémy Munsch
09dd7836e1
Fix timeslot select 2023-05-12 11:36:35 +02:00
Jérémy Munsch
402bb46b9b
Fix shop isCreneauAvailable 2023-05-12 11:28:53 +02:00
Jérémy Munsch
7033f76d44
OrderPrepareComponent 2023-05-12 10:01:23 +02:00
Jérémy Munsch
2e1b82e6f2
Slider placeholder 2023-05-12 09:01:30 +02:00
Jérémy Munsch
def7703d7f
theme upgrade 2023-05-11 14:47:45 +02:00
Jérémy Munsch
22aaa35daa
upgraded theme 2023-05-11 14:44:23 +02:00
Jérémy Munsch
5a99e26334
Better product disponibility message 2023-05-11 12:58:48 +02:00
Jérémy Munsch
e4cf01c298
Fix publication warnings 2023-05-11 12:51:40 +02:00
Jérémy Munsch
d7510f0c8d
Fix customer restored on every connection 2023-05-11 12:36:16 +02:00
Jérémy Munsch
fc0fb741f2
fix timeslotcpt 2023-05-11 12:14:07 +02:00
Jérémy Munsch
dade724563
webpack clean 2023-05-11 11:48:09 +02:00
Jérémy Munsch
5d10af5057
Fix #65 + show only when available creneaux 2023-05-11 11:37:16 +02:00
Jérémy Munsch
fa51a98d04
Fix missing cooking notive NPM dep 2023-05-11 11:37:11 +02:00
Jérémy Munsch
0f556f5458
npm update 2023-05-11 11:28:57 +02:00
Jérémy Munsch
ef1c2896fc
fix #64 Discount status update 2023-05-11 10:47:44 +02:00
Jérémy Munsch
95f8e54167
Fix #63 Policy on product inco association 2023-05-11 10:19:40 +02:00
Jérémy Munsch
30108412a6
add migration ot make sure inco pictures are empty 2023-05-11 10:04:32 +02:00
Jérémy Munsch
9ef16ef775
Fix submodule theme 2023-05-11 09:38:46 +02:00
Jérémy Munsch
e81cb02ce2
the checkout update 2023-05-11 09:29:14 +02:00
Jérémy Munsch
5746177b73
create admin add confir password 2023-05-11 09:19:54 +02:00
Jérémy Munsch
99548a0c84
Fix wrong resolveSubmit amount 2023-05-03 11:15:09 +02:00
Jérémy Munsch
403b59696c
add SystempayCreditTransactionUpdate 2023-05-03 10:41:46 +02:00
Jérémy Munsch
33e7c76830
Add resolve placeholder on Bo input 2023-05-03 10:41:34 +02:00
Jérémy Munsch
31bf60efd2
composer bump laravel-systempay + update 2023-05-03 09:12:43 +02:00
Jérémy Munsch
ca2147bf5a
Fix refund system 2023-05-03 09:04:53 +02:00
Jérémy Munsch
a312865bdf
Bo OrderController fix prepareSubmit 2023-05-02 14:31:48 +02:00
Jérémy Munsch
9950bb3d61
composer.json
remove wrong static path
2023-05-02 12:07:50 +02:00
Jérémy Munsch
53d8316131
Fix #59 2023-05-02 10:55:07 +02:00
Jérémy Munsch
fb5563eb51
Fix #60 typo paiement 2023-05-02 10:55:02 +02:00
Jérémy Munsch
0d68d4dc6a
SYNC 100 LAST Changes before recettage client 2023-04-26 16:36:37 +02:00
Jérémy Munsch
808e71143e
Sync 82 Static pages fixes from client 2023-04-26 15:25:34 +02:00
Jérémy Munsch
86adb38433
Sync 81 Fixed all Slides issues from customer 2023-04-26 14:46:49 +02:00
Jérémy Munsch
f238859798
Fix typo in OrderHasBeenRefunded 2023-04-26 14:32:37 +02:00
Jérémy Munsch
58bc2b9a31
Slide make published status always update with planificated_status 2023-04-26 12:05:35 +02:00
Jérémy Munsch
a275b8574c
Fix #57 CreateAdmin console commande wrong user type 2023-04-26 11:37:21 +02:00
Jérémy Munsch
0766296a38
Fix PDF generation 2023-04-26 11:30:42 +02:00
Jérémy Munsch
439ef4c7f2
Add missing resolve blade element 2023-04-26 10:32:12 +02:00
Jérémy Munsch
6ba2d50633
Order model getCanBeRefundAttribute fix 2023-04-26 10:31:56 +02:00
Jérémy Munsch
2c2f73672f
Refund on prepare fix 2023-04-26 10:31:39 +02:00
Jérémy Munsch
9780729fde
Resolve order fix 2023-04-26 10:28:35 +02:00
Jérémy Munsch
6f4be83970
Sync 80 Systempay Refund First test 2023-04-25 21:52:27 +02:00
Jérémy Munsch
3dcda6191c
Fix #54 2023-04-24 18:49:42 +02:00
Jérémy Munsch
99559db6b1
Fix #53 autocomplete shipping form 2023-04-24 17:52:37 +02:00
Jérémy Munsch
820a39e39f
Fix #55 Add order ref and link to bo user on confirm mail 2023-04-24 17:39:01 +02:00
Jérémy Munsch
1dee3593e2
Fixes for #56 in-shop-pickup-date blade 2023-04-24 17:29:48 +02:00
Jérémy Munsch
49f13d5dc7
Fixes #52 add ck-content classes 2023-04-24 17:19:43 +02:00
Jérémy Munsch
ec13c57179
bump kwaadpepper/image-resizer to Fix #50 2023-04-19 14:39:26 +02:00
Jérémy Munsch
443f3a3c21
Fix bug on product range display fo 2023-04-19 12:58:08 +02:00
Jérémy Munsch
0303ffb348
Closes #46 and closes #47 2023-04-19 12:55:33 +02:00
Jérémy Munsch
5f6d9fa0db
#fixes #48 2023-04-19 12:19:02 +02:00
Jérémy Munsch
cab5e65258
Systempay Refund + Handle refund on event listener and refactor 2023-04-07 11:27:35 +02:00
Jérémy Munsch
b028acd55d
Refactor isCompleteRefundWithTransaction 2023-04-07 10:15:34 +02:00
Jérémy Munsch
1d9a7f6659
Fix for intended url on bo 2023-04-06 10:03:51 +02:00
Jérémy Munsch
1fbe1f987d
bump ldc/laravel-systempay + add more response type on SystempayEventListener 2023-04-05 12:16:43 +02:00
Jérémy Munsch
b603320aee
fix for order controller message 2023-04-05 11:21:40 +02:00
Jérémy Munsch
e0d03de207
SYNC 70 systempay done ok 2023-04-05 10:56:40 +02:00
Jérémy Munsch
e49588ab14
Add missing Systempay Webhook on EventServiceProvider 2023-04-05 10:48:05 +02:00
Jérémy Munsch
c40646c1b7
Dynamic cors endpoint 2023-04-05 10:47:23 +02:00
Jérémy Munsch
2e41ac9267
SystemPay TMP 2023-04-04 13:56:03 +02:00
Jérémy Munsch
f0cb5cdd61
Fix store product 2023-04-04 13:37:13 +02:00
Jérémy Munsch
de9b0a842d
undeo product reference require (will autogenerate) 2023-04-04 13:37:13 +02:00
Jérémy Munsch
0e8ba02cf4
Fix migration 2023_05_30_000001_create_customer_stripe_column 2023-04-04 13:37:13 +02:00
Jérémy Munsch
71794701b5
IsCachedAsCollection Prevent failure on deploy 2023-04-04 13:37:12 +02:00
Jérémy Munsch
be58a967f4
Fixes #43 with a regression on CkEditorComponent 2023-04-04 11:13:44 +02:00
Jérémy Munsch
fb105d31dd
bsTooltip fix 2023-04-04 11:13:43 +02:00
Jérémy Munsch
d8b8cbc584
gitlab CI update 2023-04-03 17:40:58 +02:00
Jérémy Munsch
db3a8763cf
Fixes #41 2023-04-03 11:20:02 +02:00
Jérémy Munsch
f5f00e685a
Fix wrapAction and checkout usage to prevent saving into session the uncommited transaction model 2023-04-03 11:20:02 +02:00
Jérémy Munsch
9bc60dbaa0
Undo wrapper in transaction as it would somehow not work 2023-04-03 11:20:02 +02:00
Jérémy Munsch
6838280386
Undo payment adapter in transaction (already done) 2023-04-03 11:20:02 +02:00
Jérémy Munsch
338c7899cd
Fix for inShop transaction (use DB transaction) 2023-04-03 11:20:01 +02:00
Jérémy Munsch
0787855b98
Fix for Stripe payment (use DB transaction request) 2023-04-03 11:20:01 +02:00
Jérémy Munsch
0863a06362
Fix add missing migrations and remove unwanted migrations form payment system. 2023-04-03 11:20:01 +02:00
Sebastien Hattry
9f476854c0
Add auth.json + npm audit fix 2023-04-03 11:20:00 +02:00
Sebastien Hattry
e7e3ad721e BO - Fix sort column bug 2023-03-31 10:06:45 +00:00
Jérémy Munsch
49d0e7de50
Fix ORder Controller clone 2023-03-30 11:21:14 +02:00
Jérémy Munsch
13f1868342
Fo ORder controller clone transaciton 2023-03-30 11:16:01 +02:00
Jérémy Munsch
ae218ff416
InShopPaymentAdapter fix checkoutIsResponseIsValid verifiy signature as relative 2023-03-30 11:15:48 +02:00
Jérémy Munsch
536bcde8da
OrderPolicy list post order-tunnel action where order mail_sent is true 2023-03-30 11:06:47 +02:00
Jérémy Munsch
5a08860dd6
Fix regression PaymentAdapter successRoute on signed url route 2023-03-30 10:56:04 +02:00
Jérémy Munsch
d20e9b08da
Fix ToolboxHelper:: basketObjectIsValid validation rule for tax_rate 2023-03-30 10:31:56 +02:00
Jérémy Munsch
1620123d3f
Fix throwing RuntimeException form ToolboxHelper 2023-03-30 10:04:51 +02:00
Jérémy Munsch
68217f25cb
SYNC 58 New version CN hard coded 2023-03-30 09:52:38 +02:00
Jérémy Munsch
79c70ea485
cookie session by default true on secure 2023-03-30 09:52:02 +02:00
Jérémy Munsch
4f151512d2
stylelint rc new version 2023-03-30 09:51:44 +02:00
Jérémy Munsch
c48ffdb30b
Fix IsCachedTrait 2023-03-28 14:43:59 +02:00
Jérémy Munsch
f90e808cd0
Fix Signed url to be relative a cause de serveurs de merde 2023-03-24 19:15:15 +01:00
Jérémy Munsch
2b1a47f48c
fix remove forgotten file preparationForm.ts 2023-03-24 18:24:38 +01:00
Jérémy Munsch
2256ebe3b0
Attempt to handle correctly stripe parameters and warning 2023-03-24 17:51:42 +01:00
Jérémy Munsch
fe92159bec
phpcs fix Transaction Model 2023-03-24 17:18:33 +01:00
Jérémy Munsch
fba6061c1f
phpcsfix 2023-03-24 17:13:03 +01:00
Jérémy Munsch
78da14d357
phpcsfix OrderController Fo 2023-03-24 17:12:22 +01:00
Jérémy Munsch
1f85380a2c
Fixes #38 Fix ContactMessagesController search 2023-03-24 17:05:25 +01:00
Jérémy Munsch
b1269cfa8c
Fixes #37
- Ignore charge.refund.updated' Stipe event
- Make order searchable with payment foreign_id or foreign_refund_id on Bo
2023-03-24 16:43:52 +01:00
Jérémy Munsch
d4a98afc45
phpcs fix 2023-03-24 16:03:57 +01:00
Jérémy Munsch
d90697de7c
remove getShop to use fake relation 2023-03-24 16:02:24 +01:00
Jérémy Munsch
373ad67b8a
Fix line2 on ShippingFormRequest 2023-03-24 15:55:52 +01:00
Jérémy Munsch
5203f8e5b3
Fixes #36 Sync 53 Soft Delete Timeslot (libération du timeslot pour une autre commande) 2023-03-24 15:55:41 +01:00
Jérémy Munsch
a731e2a5ee
Fix payment mode update request 2023-03-24 15:06:44 +01:00
Jérémy Munsch
e612b9490f
Fix isCachedCollection 2023-03-24 15:06:31 +01:00
Jérémy Munsch
2bdae2ec6b
Fixes #35 2023-03-24 13:59:21 +01:00
Jérémy Munsch
64b00ed102
Fixes #28 Slide create 2023-03-24 13:54:05 +01:00
Jérémy Munsch
d52c2f8734
Fixes #33 Products range filter 2023-03-24 13:52:13 +01:00
Jérémy Munsch
8bab835d61
Fixes #31 #32 Change incoInputs
- Inco products servings are nullable and are now strings
- Inco products servings type are nullable
2023-03-24 13:46:14 +01:00
Jérémy Munsch
180a5d9f55
fix unique constrains migration 2023-03-24 13:33:01 +01:00
Jérémy Munsch
28213946df
Sync 52 add addresse line 2 2023-03-22 18:41:20 +01:00
Jérémy Munsch
4194cd559a
Add invoices to notifications 2023-03-22 18:03:40 +01:00
Jérémy Munsch
e7e5ce5cfe
Sync 51 Transactions PDF + multiple fixes 2023-03-22 17:17:55 +01:00
Jérémy Munsch
59ce99524a
Minicrud update to handle attributes 2023-03-20 16:28:20 +01:00
Jérémy Munsch
499a8ff66e
- Allow customer to soft delete and full delete
- Fixed customer info update
2023-03-20 12:52:55 +01:00
Jérémy Munsch
7c4fffd25e
fix order shipped basket as empty on DB 2023-03-20 09:27:05 +01:00
Jérémy Munsch
279f0f40f9
Add job delete old customers that arent verified 2023-03-20 09:09:43 +01:00
Jérémy Munsch
913836d7b4
LoginController set last connection on login 2023-03-20 08:58:44 +01:00
Jérémy Munsch
cd22eb5767
fix refresh sitemap config 2023-03-20 08:42:56 +01:00
Jérémy Munsch
b08c4ba454
- Update jobs folder
- Remove RefreshSiteMap static job
2023-03-20 08:42:09 +01:00
Jérémy Munsch
4eca6543c8
Added IsCached model trait 2023-03-17 17:32:18 +01:00
Jérémy Munsch
e60d8c293d
bo order change status autorize 2023-03-17 16:58:10 +01:00
Jérémy Munsch
30fbe680db
Order policy fix bo issue for pending stripe transaction 2023-03-17 16:43:05 +01:00
Jérémy Munsch
a1d776cefa
Fix new order has been created message to bo user 2023-03-17 16:30:24 +01:00
Jérémy Munsch
7cf6546f81
Boutique transaction cannot be refund 2023-03-17 16:10:18 +01:00
Jérémy Munsch
bc932ff48f
Hanlde IPN call balance report 2023-03-17 16:09:49 +01:00
Jérémy Munsch
b772790b76
AppServiceProvider : Remove Stripe 'CASHIER_CURRENCY' to be static 'euro' 2023-03-17 12:36:53 +01:00
Jérémy Munsch
c22859d810
Refactor name destroyCurrentBasket 2023-03-17 12:36:01 +01:00
Jérémy Munsch
ac64ea1d7e
Add unique constrains 2023-03-17 10:25:09 +01:00
Jérémy Munsch
276003152a
add foreign transaction ids on bo order view 2023-03-16 16:48:35 +01:00
Jérémy Munsch
b8a20d6a18
confirm on payment system status change 2023-03-16 16:38:48 +01:00
Jérémy Munsch
78fc610454
Step 2 inShop refund OK 2023-03-16 13:17:11 +01:00
Jérémy Munsch
7e21eff64c
Step 2 Stripe refund OK 2023-03-16 12:57:29 +01:00
Jérémy Munsch
0b615de4c3
Step 2 full Stripe refund ok 2023-03-16 11:43:21 +01:00
Jérémy Munsch
f371cd118d
Step 2
- Order can be cancelled even if payed
- Moved specific TS definitions on bo directory
- Minicrud fix prepended slash
- Vue component Order Prepare
- Validate Basket Item format on server side (ToolboxHelper)
- Fixed transaction without webhooks
- Added Order preparation steps
- Stripe Order refund
- Add preventLazyLoad to AppServiceProvider (partial strict mode)
2023-03-15 17:51:01 +01:00
Jérémy Munsch
47ca160dd8
flash messages bo to bo only 2023-03-13 14:34:35 +01:00
Jérémy Munsch
399a0cd377
switch to redirect intended url after login 2023-03-13 14:07:37 +01:00
Jérémy Munsch
1815df9201
Set cashier envars to config at runtime 2023-03-13 13:51:19 +01:00
Jérémy Munsch
eea7479319
session flash to bo only changes 2023-03-13 13:32:00 +01:00
Jérémy Munsch
f36c617f4f
Stripe payment + webhooks OK for checkout system 2023-03-13 11:45:23 +01:00
Jérémy Munsch
6bbe6d70e5
Add foreign id to transaction (prefix with payment mode) 2023-03-13 10:31:52 +01:00
Jérémy Munsch
24f5fc235b
Stripe WebHooks Partial OK 2023-03-10 17:45:03 +01:00
Jérémy Munsch
e201833635
Moved LogVerifiedUser to Listeners 2023-03-10 17:19:39 +01:00
Jérémy Munsch
4c4aa6143d
Moved PasswordHasReset to Listeners 2023-03-10 17:18:40 +01:00
Jérémy Munsch
0b21b8f133
Use transaction instead of order on success route checkout payment 2023-03-10 15:11:53 +01:00
Jérémy Munsch
eaacb4d5af
Fixed issue with selecting timeslots 2023-03-10 09:21:05 +01:00
Jérémy Munsch
bfe4bd609f
Validate inShop transaction when settings order inshop as retrieved. 2023-03-09 17:17:36 +01:00
Jérémy Munsch
8473683d10
Order model fix type on flash message 2023-03-09 17:06:14 +01:00
Jérémy Munsch
0ac449d097
Refactor number format to Toolbox SYNC 47 2023-03-09 17:04:53 +01:00
Jérémy Munsch
d7edd72ec5
add amounts to order details 2023-03-09 16:53:00 +01:00
Jérémy Munsch
4c4f97387c
Refactor currency usage 2023-03-09 16:52:41 +01:00
Jérémy Munsch
a312b862b2
Bo order info display enhance 2023-03-09 16:36:48 +01:00
Jérémy Munsch
7a9f52041f
fix typo on Order Model 2023-03-09 15:04:24 +01:00
Jérémy Munsch
f2bf1513aa
Fixed mix manifest (prepare for Vite) 2023-03-09 12:31:44 +01:00
Jérémy Munsch
b757d7df9d
Payment system PART2 : Stripe OK SYNC 46
- changed order pk column to id
- refactored and fixed inShop
- using php Money for transaction
2023-03-09 12:07:40 +01:00
Jérémy Munsch
3906c96a6e
Payment system PART1 : in Shop OK SYNC 46 2023-03-09 11:58:43 +01:00
Jérémy Munsch
d3253333ba
419 autorefresh 2023-03-09 11:58:43 +01:00
Jérémy Munsch
b910075b7f
Revert StaticPageController liker before refresh-sitemap bump 2023-03-09 11:58:43 +01:00
Jérémy Munsch
becd807175
fix webpack mix copy assets 2023-03-09 11:58:42 +01:00
Jérémy Munsch
853d813282
base project mise en conformitée SYNC 45 2023-03-09 11:58:42 +01:00
Jérémy Munsch
68ec206f02
upgrade and fix sitemap to 1.1.0 2023-03-09 11:58:42 +01:00
Jérémy Munsch
80631d4118
dont handle home on static page show fo 2023-03-09 11:58:42 +01:00
Jérémy Munsch
4a3ebac562
throw runtime error with static pages only if debug 2023-03-09 11:58:41 +01:00
Jérémy Munsch
dab6f5aa0e
Fav icon system 2023-03-09 11:58:41 +01:00
Jérémy Munsch
71c30290ed
Use ldc web status checker package 2023-03-09 11:58:41 +01:00
Jérémy Munsch
687ac68e09
update env.example 2023-03-09 11:58:40 +01:00
Jérémy Munsch
6e6c140eaf
Rangement des models SYNC 40 2023-03-09 11:58:40 +01:00
Jérémy Munsch
f66d470b3d
Enforce policies on Delivery Mode 2023-03-09 11:57:21 +01:00
Jérémy Munsch
1c9bbf5f41
Settings for PaymentMode (Still need system pay values) 2023-03-09 11:57:20 +01:00
Jérémy Munsch
500e0c18af
Add password to mini crud inputs 2023-03-09 11:57:20 +01:00
Jérémy Munsch
b9262fc0c0
Update minicrud request to allow picture requirement validation rule 2023-03-09 11:57:20 +01:00
Jérémy Munsch
a1f94db388
update minicrud to force using laravel cast (allow using encrypted for example) 2023-03-09 11:57:20 +01:00
Jérémy Munsch
34ca7c8238
minicrud image input fix => get ImageInputComponent from calliope 2023-03-09 11:57:19 +01:00
Jérémy Munsch
bc9975b76e
Fix puslished Elemets warnings on Controller general middleware 2023-03-09 11:57:19 +01:00
Jérémy Munsch
7a9c71da37
remove hello from bo layout 2023-03-09 11:57:18 +01:00
Jérémy Munsch
fb98660ed6
Delivery Mode changed column to mode as PK 2023-03-09 11:57:18 +01:00
Jérémy Munsch
9022c7bedd
Fix for fresh install + remove fucking seeder de MERDE 2023-03-09 11:55:27 +01:00
Vincent BRIDIER
d42aa9cb23 update submodule 2023-03-09 09:11:47 +00:00
Jérémy Munsch
f07a8f62d5
Some fixes on contact controller 2023-02-27 17:20:27 +01:00
Jérémy Munsch
d227ca624b
Some fixes revertion on Fo controllers 2023-02-27 17:15:08 +01:00
Jérémy Munsch
38a5e9adea
hide payement status 2023-02-27 16:53:06 +01:00
Jérémy Munsch
96d11aca11
fix route loading 2023-02-27 09:34:15 +01:00
Jérémy Munsch
6ede878a48
fix typo 2023-02-27 09:25:32 +01:00
Jérémy Munsch
a3732674b1
Bs customize eror messages 2023-02-24 18:02:32 +01:00
Jérémy Munsch
3c7c1f52f0
Fix cars routes and controller + model binding for cards and tags 2023-02-24 16:04:52 +01:00
Jérémy Munsch
abe383d79b
reorder routes + fix cards routes (change post to Get) 2023-02-24 15:51:32 +01:00
Jérémy Munsch
03c8b4b57c
fix notifications mail without generic logo 2023-02-24 15:30:13 +01:00
Jérémy Munsch
4a1ab3e536
allow unauthenticated customer tu use basket system 2023-02-24 15:12:49 +01:00
Jérémy Munsch
a1b5cf3e11
keep customer basket after login 2023-02-24 15:12:35 +01:00
Jérémy Munsch
84aa9df1dc
add verified for user to access tunnel 2023-02-24 15:12:03 +01:00
Jérémy Munsch
8c0760ef9b
header login dropdown on bo 2023-02-24 14:29:57 +01:00
Jérémy Munsch
23c8826d4e
Fixes #25 only activated customers can order things 2023-02-24 12:13:20 +01:00
Jérémy Munsch
33d2f4e573
Fixes #24 XmlInco Full process of pictures 2023-02-24 11:44:32 +01:00
Jérémy Munsch
97361f7a56
REmove unused blade directive 2023-02-24 09:32:27 +01:00
Jérémy Munsch
f6e8726096
fix mail theme compilation 2023-02-23 19:03:15 +01:00
Jérémy Munsch
c8c3393630
mail theming 2023-02-23 18:46:28 +01:00
Jérémy Munsch
515d5400ab
customize emails 2023-02-23 18:29:12 +01:00
Jérémy Munsch
95a060dba3
remove unsued config params 2023-02-23 18:23:37 +01:00
Jérémy Munsch
4b67509c64
remove file manager vendor blades 2023-02-23 18:16:06 +01:00
Jérémy Munsch
fb43e76037
phpcs 2023-02-23 18:12:32 +01:00
Jérémy Munsch
ba6c249ec5
Xml Inco Reader a Tester 2023-02-23 18:10:42 +01:00
Jérémy Munsch
2981f3a540
CrmUserProvider handle MustValidate trait 2023-02-23 18:07:54 +01:00
Jérémy Munsch
3ffffc51be
REmove irrelevant download controller 2023-02-23 18:07:05 +01:00
Jérémy Munsch
e35c13a3db
Remove basket on login 2023-02-23 17:54:32 +01:00
Jérémy Munsch
84bbbbf025
phpcsfix 2023-02-23 17:54:22 +01:00
Jérémy Munsch
fabf86778b
Remove role from create admin command 2023-02-23 17:51:26 +01:00
Jérémy Munsch
fd8db72774
All OK 2023-02-23 17:47:30 +01:00
Jérémy Munsch
6c39b88daf
Fix product minimum price (using reductions) 2023-02-23 17:47:24 +01:00
Jérémy Munsch
6e3c8ec8a5
Fix bug onn creneau validation order tunnel 2023-02-23 17:34:27 +01:00
Jérémy Munsch
7a4ecef362
bs tooltip delay 2023-02-23 17:09:10 +01:00
Jérémy Munsch
8dec9842df
fix config picture sizes 2023-02-23 15:05:38 +01:00
Jérémy Munsch
dab427f1d3
Fix bo product form labels 2023-02-23 14:53:52 +01:00
Jérémy Munsch
0a6eb9b890
Triage tags actions out of product controller 2023-02-23 13:14:29 +01:00
Jérémy Munsch
34af665395
Triage ranges actions out of product controller 2023-02-23 13:12:00 +01:00
Jérémy Munsch
bcb8a28137
Fixes #14 Add warning if none delivery mode is available. 2023-02-23 12:41:01 +01:00
Jérémy Munsch
a77c6f3efa
fix ProductCard Many to Many vue componemt compilation 2023-02-23 10:51:52 +01:00
Jérémy Munsch
2c374fb502
Fixed #17 end 2023-02-23 10:32:42 +01:00
Jérémy Munsch
af0b9c5838
enhance timeslot settings form and fix validation for min prep time (required with any duration) optional none 2023-02-23 10:20:18 +01:00
Jérémy Munsch
8c4ca3d2aa
Add shop name to timeslots settings 2023-02-22 17:54:13 +01:00
Jérémy Munsch
c9c52d49ed
Fix UpdateTimeslotRequest 2023-02-22 17:52:37 +01:00
Jérémy Munsch
5f24fdb4b4
Refactor UpdateTimeslotRequest 2023-02-22 17:49:22 +01:00
Jérémy Munsch
1455242f5e
Timeslot add max value to min_order_preparation 144H 2023-02-22 17:44:13 +01:00
Jérémy Munsch
b221b35391
fix time slot ranges orders rules 2023-02-22 16:52:33 +01:00
Jérémy Munsch
e3e801d6d5
enhanced timeslot form display 2023-02-22 16:52:14 +01:00
Jérémy Munsch
81b226d25b
Move Session flash after breadcrumb 2023-02-22 16:28:18 +01:00
Jérémy Munsch
7564c25e9d
Re Add tags associated products display 2023-02-22 16:25:47 +01:00
Jérémy Munsch
0f4c859554
Fixes #19 Tags slug 2023-02-22 15:52:44 +01:00
Jérémy Munsch
59d56abc87
Fixes #13: Range deletion 2023-02-22 15:48:03 +01:00
Jérémy Munsch
c95d100611
Fixes #16 Force and disable Start on Index starred products 2023-02-22 15:44:21 +01:00
Jérémy Munsch
a9a8bb9c79
Refactor TimeslotSettings management with shops 2023-02-22 15:16:52 +01:00
Jérémy Munsch
5414ef54db
Fix timeslot generation for new shops 2023-02-22 15:00:02 +01:00
Jérémy Munsch
cdd69b66d0
CSP remove unrequired policies 2023-02-22 14:59:29 +01:00
Jérémy Munsch
74dc29781b
Fixed shop remove unused parameters 2023-02-22 11:14:57 +01:00
Jérémy Munsch
4168551267
fixed config debug 2023-02-22 11:13:19 +01:00
Jérémy Munsch
ba52c85e42
Fixed CSP 2023-02-22 11:12:51 +01:00
Jérémy Munsch
2b09eae3e1
Remove unused fields from shop 2023-02-21 09:39:46 +01:00
Jérémy Munsch
7ad3ee9f2e
fixed #10 2023-02-20 17:56:40 +01:00
Jérémy Munsch
c1d542cd38
Fixed adult rule 2023-02-20 17:51:52 +01:00
Jérémy Munsch
c8bd8e2dd6
ProductsCards Vue fix 2023-02-20 13:43:42 +01:00
Jérémy Munsch
5ec189b645
drag and drop list associated products 2023-02-17 18:37:04 +01:00
Jérémy Munsch
a8955ea980
fixed display order 2023-02-17 18:37:04 +01:00
Jérémy Munsch
ec00bfc061
Bug fix in TranslationServiceProvider 2023-02-17 18:37:03 +01:00
Jérémy Munsch
6ffe20651a
product cards change 2023-02-17 18:37:03 +01:00
Jérémy Munsch
5ab24d021c
Release footer fix on stock date 2023-02-17 18:37:03 +01:00
Alexis Gousseau
b00d7fc1d8 add image resiser in slider 2023-02-17 17:31:24 +01:00
Alexis Gousseau
08c617a43e add products/ranges card 2023-02-17 10:37:20 +01:00
Jérémy Munsch
7447bfae49
All ok jemerey 2023-02-16 17:54:48 +01:00
Jérémy Munsch
a930a6edbf
npm update 2023-02-16 17:35:01 +01:00
Jérémy Munsch
cef265728e
composer update 2023-02-16 17:32:10 +01:00
Jérémy Munsch
be3cda8574
Better menu mobile add riverskies/laravel-mobile-detect 2023-02-16 17:30:43 +01:00
Jérémy Munsch
7aac8fcbca
Recaptcha ok 2023-02-16 16:59:51 +01:00
Jérémy Munsch
fd955c15fd
ended tunnel 2023-02-16 15:01:22 +01:00
Alexis Gousseau
620ce0ef3c TMP: test 2023-02-16 12:26:34 +01:00
Jérémy Munsch
95f4a3372f
Tunnel fixes 2023-02-15 17:51:13 +01:00
Alexis Gousseau
e21018d98d add tags.index + tags.show 2023-02-15 17:35:48 +01:00
Jérémy Munsch
4bd8761682
timeslote bs color 2023-02-15 15:56:05 +01:00
Jérémy Munsch
2dc9244226
add new gitlab configuration 2023-02-15 15:05:34 +01:00
Jérémy Munsch
3aa8caf539
image resizer 2023-02-15 15:03:36 +01:00
Alexis Gousseau
352b1fc5b8 add image to general config 2023-02-15 14:53:02 +01:00
Alexis Gousseau
871a40d0a3 set fo breadcrumbs 2023-02-15 12:10:33 +01:00
Alexis Gousseau
d01ff65c6a Static pages : set title to require 2023-02-15 12:10:15 +01:00
Jérémy Munsch
9f1e79eae1
PAginator 2023-02-15 09:22:55 +01:00
Alexis Gousseau
5898bdd087 Order notifications/mails 2023-02-14 16:41:48 +01:00
Jérémy Munsch
a5fa7d51de
fix confirm module 2023-02-14 16:29:11 +01:00
Alexis Gousseau
891fea9d58 fix mail contact 2023-02-14 15:49:02 +01:00
Jérémy Munsch
d29f664737
fix confirm module 2023-02-14 15:36:25 +01:00
Jérémy Munsch
4aaf428202
Force order clone before save to prevent weird session save state 2023-02-14 13:22:16 +01:00
Jérémy Munsch
1815f72648
Fix make CNC order 2023-02-14 13:22:16 +01:00
Jérémy Munsch
35974c391b
Fix BO layout 2023-02-14 13:22:16 +01:00
Alexis Gousseau
4d746a6c85 update routes,views on products/ranges/search pages 2023-02-13 17:42:34 +01:00
Alexis Gousseau
c29a727a24 Add/fix notifications on customer's account action 2023-02-13 14:57:01 +01:00
Alexis Gousseau
a7defcfdb7 Change redirect route's name after update user's data 2023-02-10 15:16:57 +01:00
Alexis Gousseau
c19ae8f690 fix breakpoint bootstrap in TaggableDropdownComponent 2023-02-10 11:44:36 +01:00
Jérémy Munsch
676b2d0797
theme config resize logo 2023-02-09 17:50:24 +01:00
Alexis Gousseau
1c96b2253d validate data from register form 2023-02-09 14:49:51 +01:00
Jérémy Munsch
3c23b8aee2
Remove file manager traces 2023-02-08 16:56:01 +01:00
Jérémy Munsch
b594590b88
ORders completed crud + order validation system with notifications 2023-02-08 16:49:28 +01:00
Jérémy Munsch
5aac86e2b1
Fix products crud 2023-02-08 15:34:18 +01:00
Jérémy Munsch
50466a79b3
orders.index fix and remove useless columns 2023-02-08 12:34:04 +01:00
Jérémy Munsch
82199d2369
layout fix 2023-02-08 12:21:01 +01:00
Jérémy Munsch
00ef145a1e
Delivery mode fix 2023-02-08 12:12:42 +01:00
Jérémy Munsch
03c9cd528d
SYNC 31 Global share upgrades 2023-02-08 12:10:10 +01:00
Jérémy Munsch
f0b839e9e9
Add nonte on StaticPageGlobal 2023-02-08 11:55:40 +01:00
Jérémy Munsch
225376b3d1
Remove sliders to keep only slides (One slider boutique only) 2023-02-08 11:53:42 +01:00
Jérémy Munsch
704e8b040b
submodule theme update 2023-02-08 11:21:26 +01:00
Jérémy Munsch
255bbf59d7
Merge branch 'issue-18' into upgrade 2023-02-08 11:18:15 +01:00
Jérémy Munsch
81e0be876f
REsponsive theme fixes 2023-02-08 10:30:29 +01:00
Jérémy Munsch
d1d637bf03
bo layout fix 2023-02-08 10:19:54 +01:00
Jérémy Munsch
c75492a969
footer fix and therefore fix a redirect error 2023-02-08 10:11:49 +01:00
Jérémy Munsch
93c94dca8b
Add sentry 2023-02-08 10:02:03 +01:00
Jérémy Munsch
67185c5cc3
Reorder and add missing bo breadcrumbs 2023-02-08 09:56:30 +01:00
Jérémy Munsch
9b69c8b012
Discount crud 2023-02-08 09:40:27 +01:00
Jérémy Munsch
058ab2ebf1
Fix responsive layout menus 2023-02-08 09:11:01 +01:00
Jérémy Munsch
fe80aad838
Contacts crud 2023-02-08 08:45:22 +01:00
Jérémy Munsch
eb0a772659
tags crud 2023-02-07 16:55:02 +01:00
Jérémy Munsch
745c9501fe
indexes fix 2023-02-07 16:49:05 +01:00
Jérémy Munsch
91f7812f84
ranges crud 2023-02-07 16:44:32 +01:00
Jérémy Munsch
5ef07d400e
products crud 2023-02-07 16:39:01 +01:00
Jérémy Munsch
818c5df641
Shops form fix 2023-02-07 15:02:49 +01:00
Jérémy Munsch
70609378bd
add optionnal value to partials inputs integer 2023-02-07 14:59:31 +01:00
Jérémy Munsch
9867755696
Add append text options to partials inputs integer 2023-02-07 14:38:12 +01:00
Jérémy Munsch
066f8109ad
slide foreign form fix 2023-02-07 13:23:22 +01:00
Jérémy Munsch
02d6f0ae30
static page crud create fix 2023-02-07 13:15:34 +01:00
Jérémy Munsch
32c502818d
table col sorter fix 2023-02-07 12:35:48 +01:00
Jérémy Munsch
47e0524140
Static page crud 2023-02-07 12:20:46 +01:00
Jérémy Munsch
c42194356f
slides form comment 2023-02-07 11:03:49 +01:00
Jérémy Munsch
3149fa557b
breadcrumb layout fix 2023-02-07 10:52:42 +01:00
Jérémy Munsch
6f2d02e4f1
Slide and slide crud fix 2023-02-07 10:50:48 +01:00
Jérémy Munsch
5cf13785bd
webpack mix 2023-02-07 09:24:21 +01:00
Jérémy Munsch
9b9fd88e6b
Layout fix for flash message 2023-02-07 08:50:21 +01:00
Jérémy Munsch
f78c14d9f7
Auth form fix 2023-02-06 17:32:40 +01:00
Jérémy Munsch
b7a6fecc26
General config upgrade 2023-02-06 17:14:58 +01:00
Jérémy Munsch
dceba75e3b
Upgrade Shops crud for reference 2023-02-06 15:52:49 +01:00
Jérémy Munsch
6ed0a4fdf5
CKEDITOR update to 36 2023-02-06 14:08:47 +01:00
Jérémy Munsch
4ef00d6fea
Remove ckeditor submodules unused with standard ckeditor 2023-02-06 14:06:36 +01:00
Jérémy Munsch
1bd4983cde
Merge table colsorter fix form lionor 2023-02-06 14:00:33 +01:00
Jérémy Munsch
3b905203a7
Fixes issue #18 typos 2023-02-06 13:51:29 +01:00
Jérémy Munsch
3c80fe8c4c
Fix weekday translation 2023-02-02 18:13:29 +01:00
Jérémy Munsch
c2aa74e1ce
Fix product tax rate 2023-02-02 18:03:50 +01:00
Jérémy Munsch
1f9bc0f04c
theme config merge fix 2023-02-02 16:57:12 +01:00
Jérémy Munsch
7b54a77d7d
fix contact mail model event 2023-02-02 16:23:51 +01:00
Jérémy Munsch
ff5bf2418e
change weekday enum value for mairet 2023-02-02 16:01:02 +01:00
Jérémy Munsch
9816bb93d5
changed submodule url 2023-02-02 15:51:05 +01:00
Jérémy Munsch
237a17d815
fixed theme sliders 2023-02-02 15:09:42 +01:00
Jérémy Munsch
2ebcf417c0
fix migration for mairet dev 2023-02-02 14:34:31 +01:00
Jérémy Munsch
74f88ffc0e
change git submodules remotes to https 2023-02-02 14:13:49 +01:00
Jérémy Munsch
3d3f79cbdd
fix product form 2023-02-02 12:14:34 +01:00
Olivier Legat
a1a93195ac
fix backend extend 2023-02-02 12:06:50 +01:00
Olivier Legat
8349bf5ae7
fix modules list, fix blade extension in login blade, update package lock 2023-02-02 11:54:12 +01:00
Jérémy Munsch
29f07ad69a
added forgottent submodule theme 2023-02-02 11:26:29 +01:00
Jérémy Munsch
c23659c600
fix migration script 2023-01-31 09:04:20 +01:00
Jérémy Munsch
c944141342
SYNC 25
Theme Hudge files and foder reorder
2023-01-30 17:46:30 +01:00
Jérémy Munsch
04178116c9
bump composer 2023-01-30 12:33:36 +01:00
Jérémy Munsch
d2272a7a13
bump npm 2023-01-30 12:32:46 +01:00
Jérémy Munsch
3b243d6cca
SYNC A10
Static page + crud bo + fix Filemanager wysiwyg
2023-01-26 11:21:48 +01:00
Jérémy Munsch
75b33bc116
add ltr attr on blade html layout 2023-01-25 14:31:21 +01:00
Jérémy Munsch
f7ea8d0004
ORder Validation SYNC 23 2022-12-20 16:52:44 +01:00
Jérémy Munsch
b0a72941b5
Front customer backoffice SYNC 22 2022-12-20 13:02:55 +01:00
Jérémy Munsch
d681d55d68
Order validated Sync 21 2022-12-20 11:32:06 +01:00
Jérémy Munsch
529651895f
Order up to final click SYNC 20 2022-12-19 16:20:00 +01:00
Jérémy Munsch
d18a589869
fix bo timeslot closing 2022-12-07 12:29:26 +01:00
Jérémy Munsch
fb45dd4ace
general settings labels migration update 2022-12-07 10:38:55 +01:00
Jérémy Munsch
6869c289f5
Products filter bo 2022-12-06 16:50:32 +01:00
Jérémy Munsch
37725e43fe
Contact message system 2022-12-06 12:59:34 +01:00
Jérémy Munsch
51bb5be052
RGPD remove old customers and remove old ORders 2022-12-06 11:53:41 +01:00
Jérémy Munsch
e7fbe920e1
Remove order line + shipping point select 2022-12-05 17:51:18 +01:00
Jérémy Munsch
5aa0a479fd
Add Delivery Mode to orders 2022-12-05 15:21:54 +01:00
Jérémy Munsch
7d31eb4c72
Show has discount on product.edit 2022-12-05 14:29:24 +01:00
Jérémy Munsch
2d4f9111f4
Product starred fix 2022-12-05 14:14:51 +01:00
Jérémy Munsch
1b0a9d2a18
Handle policies SYNC 15 2022-12-02 14:55:16 +01:00
Jérémy Munsch
b2221b4fe8
Delivery Mode 2022-12-02 11:46:31 +01:00
Jérémy Munsch
a3fa02cfd5
Rangement enums + start delivery mode SYNC 14 2022-12-02 10:37:01 +01:00
Jérémy Munsch
950ff85cbd
single optin route triage 2022-11-30 17:05:37 +01:00
Jérémy Munsch
539f02539d
tunnel stepper SYNC 13 2022-11-30 17:03:43 +01:00
Jérémy Munsch
9fefc205fd
Basket filter unavailable products 2022-11-30 16:36:27 +01:00
Jérémy Munsch
94d84e4251
console.log on debug 2022-11-30 15:40:52 +01:00
Jérémy Munsch
3edc643bb3
Add Inco values display on BO 2022-11-30 11:43:05 +01:00
Jérémy Munsch
fdf548fd0f
Suppression setting orders_display_prices_for_auth SYNC 12 2022-11-29 16:47:22 +01:00
Jérémy Munsch
c8e21e7df8
Calulcator Basktet OK 2022-11-29 15:47:46 +01:00
Jérémy Munsch
e0718decca
Basket Calculation OK SYNC 11 2022-11-29 15:02:38 +01:00
Jérémy Munsch
a243880c58
remove fancybox 2022-11-28 10:33:42 +01:00
Jérémy Munsch
fc1f071262
fix nested view load pathes SYNC 10 2022-11-24 15:30:22 +01:00
Jérémy Munsch
4be962e01d
Change Product <-> Discount association, A product can now have only one discount associated. 2022-11-24 11:21:37 +01:00
Jérémy Munsch
b3f79cb114
git lab ci update 2022-11-24 11:17:43 +01:00
Jérémy Munsch
33b523ff82
Order Tunnel + Basket unit prices SYNC 9 2022-11-23 15:48:07 +01:00
Jérémy Munsch
7f456569b2
Rename products an ranges routes + fix add to basket actions SYNC 8 2022-11-23 13:00:44 +01:00
Jérémy Munsch
efe25f958a
Fix route orders 2022-11-23 11:59:25 +01:00
Jérémy Munsch
d3a963343c
remove quick orders SYNC 7 2022-11-23 11:48:47 +01:00
Jérémy Munsch
ac032cd6e6
fix git submodule add ckeditor plugins 2022-11-23 11:15:47 +01:00
Jérémy Munsch
94ae431d55
BASKET OK SYNC 6 2022-11-23 11:10:40 +01:00
Jérémy Munsch
f043eeaa8d
Stylelint upgrade add sccs base rules 2022-11-22 15:49:33 +01:00
Jérémy Munsch
001691e342
npm update 2022-11-21 11:53:35 +01:00
Jérémy Munsch
ee17d3c445
CART TEMP SYNC 5 2022-11-18 18:21:47 +01:00
Jérémy Munsch
7564d9eba3
Contact page ok SYNC 4 2022-11-17 17:39:54 +01:00
Jérémy Munsch
bde869120e
Sliders SYNC3 2022-11-17 14:55:03 +01:00
Jérémy Munsch
e28970ba13
Remove basic theme from boutique engine
Theme is now a submodule on boutique engine on resources/views/front
2022-11-16 18:42:20 +01:00
Jérémy Munsch
149b54f3ed
Swiper slider test 2022-11-16 17:41:13 +01:00
Jérémy Munsch
f99e259bd5
Tons of fixes Refonte SYNC 1
- Authentification System OK
2022-11-16 12:47:41 +01:00
Jérémy Munsch
ffe82091fb
bo layout navbar redirect to front 2022-11-08 16:05:45 +01:00
Jérémy Munsch
7b48bb605a
yield title on bo layout 2022-11-08 16:04:16 +01:00
Jérémy Munsch
527d0eb828
phpcbf pass 2022-11-08 15:59:16 +01:00
Jérémy Munsch
aa98594f20
gitlab ci update 2022-11-08 15:49:59 +01:00
Jérémy Munsch
e8773a9f38
Crud StaticPage(Content) 2022-11-08 15:41:10 +01:00
Jérémy Munsch
899bc41db5
Composer bump 2022-10-03 11:18:14 +02:00
Jérémy Munsch
3659beb7b5
Remove Regate stuff + Remove salaries stuff WIP 2022-10-03 11:03:30 +02:00
Jérémy Munsch
397434d8db
Upgrade CreateAdmin command 2022-09-30 15:11:52 +02:00
786 changed files with 63954 additions and 59453 deletions

View file

@ -11,5 +11,8 @@ trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.vue]
indent_size = 2
[*.{yml,yaml}]
indent_size = 2

View file

@ -3,7 +3,6 @@ APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=
APP_THEME=basic
LOG_CHANNEL=daily
LOG_LEVEL=debug
@ -34,10 +33,21 @@ MAIL_LIST_SERVICEWEB=equipe.web@ldc.fr
MAIL_LIST_INVALIDINCO=equipe.web@ldc.fr,vincent.pierre@ldc.fr,christelle.manceau@ldc.fr
# CRM
CRM_ENDPOINT=https://ldcldccrm:K5cJmTsG@dev.ldc-crm.fr
# Service boutique
CRM_ENDPOINT=
CRM_TOKEN=
# LDC Web Status Checker
PROJECT_UUID=
# Google Analytics
CRM_PASSWORD=
# SENTRY
SENTRY_LARAVEL_DSN=
# LDC WEB STATUS CHECKER
LDC_STATUS_CHECKER_API_KEY="MY_KEY"
LDC_STATUS_CHECKER_UUID="UUID_PROJECT"
# GOOGLE
# - ANALYTICS
ANALYTICS_GOOGLE_ID=
# - RECAPTCHA
GOOGLE_RECAPTCHA_PUBLIC=
GOOGLE_RECAPTCHA_PRIVATE=
GOOGLE_RECAPTCHA_VERSION=

41
.eslintrc.json Normal file
View file

@ -0,0 +1,41 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:vue/vue3-strongly-recommended",
"@vue/eslint-config-typescript"
],
"overrides": [
{
"files": ["*.ts", "*.vue"],
"rules": {
"no-undef": "off"
}
},
{
"files": ["*.vue"],
"rules": {
"indent": "off"
}
}
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["vue", "@typescript-eslint"],
"ignorePatterns": ["webpack.mix.js", "**/vendor/*.js", "**/vendor/**/*.js"],
"rules": {
"indent": ["error", 4, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
"vue/one-component-per-file": "off"
}
}

24
.gitignore vendored
View file

@ -1,22 +1,32 @@
/node_modules
/public/hot
/public/storage
/public/images/content
/public/images/gammes
/public/images/produits
/public/images/slides
/public/b-o/
/public/mix-manifest.json
/public/assets/*
/public/js/*
/public/css/*
/public/fonts/*
/public/content/*
/public/images/*
/public/cache/*
/public/uploads/*
mix-manifest.json
/storage/*.key
/storage/framework/laravel-excel/*
/vendor
.env
.env.backup
.env.dev
.env.prod
.phpunit.result.cache
docker-compose.override.yml
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
auth.json
workspace.code-workspace
.vscode
# IGNORE THEME SUBMODULES CHANGES
resources/theme
# Ignore compiled mail theme
resource/vendor/mail/html/themes/default.css
auth.json

View file

@ -1,46 +1,24 @@
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
include:
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'lint/lint7.3.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'security/security7.3.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'build/build7.3.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'test/test.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'deploy/deploy.yml'
stages:
- Security
- CodingStandards
composer-security:
stage: Security
script:
- local-php-security-checker --format=json --path=./composer.lock > composer-security-checker-report.json
artifacts:
when: on_failure
expose_as: 'composer-security-checker'
paths:
- composer-security-checker-report.json
allow_failure: true
npm-security:
stage: Security
script:
- audit-ci -p npm -o json > npm-security-checker-report.json
artifacts:
when: on_failure
expose_as: 'npm-security-checker'
paths:
- npm-security-checker-report.json
allow_failure: true
phpcs:
stage: CodingStandards
before_script:
- composer config gitlab-token.gitlab.com $CI_DEPLOY_USER $CI_DEPLOY_PASSWORD
- composer install
cache:
paths:
- vendor/
script:
- vendor/bin/phpcs --report=junit --report-file=phpcs-report.xml
artifacts:
when: on_failure
expose_as: 'phpcs'
paths:
- phpcs-report.xml
reports:
junit: phpcs-report.xml
allow_failure: false
- lint
- security
- build
- test
- deploy

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "resources/theme"]
path = resources/theme
url = ../boutique-cnc-themes.git

View file

@ -1,5 +1,6 @@
php:
preset: laravel
version: 8
disabled:
- no_unused_imports
finder:

43
.stylelintrc.json Normal file
View file

@ -0,0 +1,43 @@
{
"extends": [
"stylelint-config-standard-scss",
"stylelint-config-sass-guidelines",
"stylelint-config-idiomatic-order"
],
"plugins": ["stylelint-scss"],
"files": ["**/*.css", "**/*.scss"],
"ignoreFiles": [
"**/vendor/*.css",
"**/vendor/**/*.css",
"**/vendor/*.scss",
"**/vendor/**/*.scss",
"**/vendor/*.sass",
"**/vendor/**/*.sass",
"**/vendor/*.less",
"**/vendor/**/*.less"
],
"customSyntax": "postcss-scss",
"rules": {
"indentation": 4,
"string-quotes": "double",
"selector-max-id": null,
"color-no-invalid-hex": true,
"order/properties-alphabetical-order": null,
"max-nesting-depth": 5,
"selector-max-compound-selectors": 5,
"declaration-colon-space-after": "always-single-line",
"function-parentheses-space-inside": "never-single-line",
"declaration-colon-newline-after": null,
"value-list-comma-newline-after": null,
"scss/double-slash-comment-empty-line-before": [
"always",
{
"ignore": [
"between-comments",
"stylelint-commands",
"inside-block"
]
}
]
}
}

View file

@ -1,3 +1,8 @@
## 2.0.0
- Removed Regate stuff
- Removed Salaries stuff
## 1.0.1
- Change LDC CRM Client repository
- composer update

View file

@ -33,6 +33,12 @@ Project root :
```
npm install && npm run prod
```
## Theme config
- To reload theme config on engine `php artisan config:sync`
- Theme config can be dynamic in `theme.config.php` inserting `MiniCrud`inputs inside, these muste be tested.
## Back-Office
Création de compte
@ -45,3 +51,33 @@ URL : /bo
## Required systems
* supervisor
## Notes implémentation
Les pages statiques sont disponible a tout moment: `$StaticPageGlobal`
# Fonts
- Robot fonts are used in PDF generation documents.
# STRIPE
php artisan cashier:webhook
- Il faut creer les webhooks dans le bo de Stripe.
- Personaliser la page de paiement https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public
- Il faut faire des payments avec plusieurs cartes pour valider l'intégration et avoir les clés de production
https://dashboard.stripe.com/test/developers pour ensuite activer le compte https://dashboard.stripe.com/account/onboarding/
# System pay
- Il faut autoriser 194.50.38.0/24 a se co au serveur en HTTP requetes POST.
# TODO
- Allow changing e-mail quand ca sera supporté sur CRM (use soundsystem mechanism)
# FIXME
- Fix tout le code lié a Stripe suite aux evolutions
- Pour poursuivre le developpement il faut avoir acces a un compte system pay (pour vérifier suite a d'eventuelles evolutions)
- Vérifier si la transaction system pay existe pour l'annuler le cas echéant dans le tunnel de commande
(Pour éviter que le client commence une transaction dans system pay, revient en arriere, utiliser autre chose, cela fait des transactions system pay qui n'existe pas dans system pay mais que dans la boutique,
il faut qppeler system pay pour voir si la transaction existe et nettoyer ces transaction fantomes dans le tunnel de commande)
- Refactoriser du code au maximum puis tout retester
- Implementer des test unitaires au moins pour toute les methodes liés au modeles au transactions, au commandes, au différentes class de LIB

View file

@ -0,0 +1,177 @@
<?php
namespace App\Auth;
use App\Models\Customer\Customer;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider as UserProviderContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
/**
* Crm user provider to integrate nicely with Laravel
*/
class CrmUserProvider extends EloquentUserProvider implements UserProviderContract
{
/**
* The model to use, specified in auth config guard.
* @var string
*/
private $usedModel = Customer::class;
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
// * Let laravel find a model
$model = parent::retrieveByCredentials($credentials) ??
static::retrieveTrashed($credentials);
try {
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
// Si pas de compte local.
if (!$model) {
$crmCustomer = $crm->getCustomer($credentials['email']);
if (!$crmCustomer) {
// * Customer is not found on CRM.
return null;
}
$model = $this->createLocalCustomer($crmCustomer);
}
return $model;
} catch (ApiErrorException $e) {
// * Customer is not found.
if ($e->getApiError()->equals(ApiError::modelNotFound())) {
return null;
}
report($e);
Log::error('Unhandled response ', $e->getApiError()->label);
} catch (CrmException | \Exception $e) {
report($e);
// Une erreur critique est survenue avec la CRM.
return null;
} //end try
Log::error(__CLASS__ . '::retrieveByCredentials reached end of possibilities.');
return null;
}
/**
* Validate a user against the given credentials.
*
* @param UserContract $model
* @param array $credentials
* @return boolean
* @throws \RuntimeException If model provided is not a Customer or connection to server fails.
* @throws \LDC\LDCCRM\Exceptions\CrmException On CRM call error.
* @throws \Exception On CRM call error.
*/
public function validateCredentials(UserContract $model, array $credentials)
{
/** @var \Illuminate\Database\Eloquent\Model $model */
if (\get_class($model) !== $this->usedModel) {
throw new \RuntimeException('Cannot authenticate any thing else than ' . $this->usedModel);
}
try {
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
// * If has trait soft delete and customer trashed, then restore customer.
if (
\in_array(\Illuminate\Database\Eloquent\SoftDeletes::class, \class_uses_recursive($model)) and
$model->trashed()
) {
$model->restore();
}
// * Ne devrait pas survenir, $crm->login throws ApiError::authFailed()
return $crm->login($credentials['email'], $credentials['password']);
} catch (ApiErrorException $e) {
if (!$model->hashcode or !CrmCustomer::find($model->hashcode)) {
$model->forceDelete();
return false;
}
if ($e->getApiError()->equals(ApiError::authFailed())) {
return false;
}
// ! Assert Authenticable model must be valided (MustValidate trait).
if ($e->getApiError()->equals(ApiError::customerNotActivated())) {
Session::flash('warning', \trans('vous devez activer votre compte'));
return true;
}
} catch (CrmException | \Exception $e) {
report($e);
if (config('app.debug')) {
throw $e;
}
return false;
} //end try
Log::error(__CLASS__ . '::validateCredentials reached end of possibilities.');
return null;
}
/**
* Create a local customer
*
* @param \LDC\LDCCRM\Models\Customer $crmCustomer
* @return \Illuminate\Database\Eloquent\Model
* @throws \Throwable If saving to DB failed.
*/
private function createLocalCustomer(\LDC\LDCCRM\Models\Customer $crmCustomer): Model
{
/** @var \Illuminate\Database\Eloquent\Model $model */
$model = $this->usedModel::firstOrNew([
'email' => $crmCustomer->email
]);
$model->hashcode = $crmCustomer->hashcode;
$model->saveOrFail();
return $model;
}
/**
* Retrieve a trashed customer by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
* @throws \ErrorException If cannot connect to LDAP.
*/
private function retrieveTrashed(array $credentials): ?Authenticatable
{
// * Make changes
if (empty($credentials)) {
return null;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a customer, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value) {
if (!Str::contains($key, 'password')) {
$query->where($key, $value);
}
}
/** @var \App\Models\Customer|null $customer */
return $query->withTrashed()->first();
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace App\Auth;
use App\Models\Customer\Customer;
use Illuminate\Support\Str;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
/**
* Overload EloquentUserProvider to handle
* Soft deleted Customers
*/
class ReactivateCustomerProvider extends EloquentUserProvider implements UserProvider
{
/**
* Retrieve a customer by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
* @throws \ErrorException If cannot connect to LDAP.
*/
public function retrieveByCredentials(array $credentials)
{
// * Make changes
if (empty($credentials)) {
return null;
}
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a customer, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value) {
if (!Str::contains($key, 'password')) {
$query->where($key, $value);
}
}
/** @var \App\Models\Customer|null $customer */
return $query->withTrashed()->first();
}
/**
* Validate a customer against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $customer
* @param array $credentials
* @return boolean
* @throws \ErrorException If model provided is not a Customer or connection to server fails.
*/
public function validateCredentials(Authenticatable $customer, array $credentials)
{
/** @var \App\Models\Customer\Customer $customer */
if ($customer instanceof Customer) {
throw new \ErrorException('Cannot authenticate any thing else than ' . Customer::class);
}
$plain = $credentials['password'];
/** @var \App\Models\Customer $customer */
if ($this->hasher->check($plain, $customer->getAuthPassword())) {
// * Restore customer
return boolval($customer->restore());
}
return false;
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Carbon;
class IntToEpochCast implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
*/
public function get($model, string $key, $value, array $attributes)
{
// phpcs:enable
if (\is_null($value)) {
return null;
}
$epoch = Carbon::createFromTimestamp(0)->timezone('utc');
$time = Carbon::now()->timezone('utc')->parse(\intval($value));
$minutesDiff = $time->diffInMinutes($epoch);
$hoursDiff = str_pad(floor($minutesDiff / 60), 2, '0', STR_PAD_LEFT);
$minutesDiff = str_pad($minutesDiff % 60, 2, '0', STR_PAD_LEFT);
return "{$hoursDiff}:{$minutesDiff}";
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
*/
public function set($model, string $key, $value, array $attributes)
{
// phpcs:enable
if (\is_null($value) or !\is_string($value)) {
return null;
}
$time = explode(':', $value);
$time = $time ? $time : [];
return Carbon::createFromTimestamp(0)->timezone('utc')
->setTime($time[0] ?? 0, $time[1] ?? 0)->timestamp;
}
}

View file

@ -2,9 +2,11 @@
namespace App\Console\Commands;
use App\Enums\UserRole;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateAdmin extends Command
{
@ -13,14 +15,14 @@ class CreateAdmin extends Command
*
* @var string
*/
protected $signature = 'create:admin';
protected $signature = 'user:create';
/**
* The console command description.
*
* @var string
*/
protected $description = 'creates an admin user upon password entry';
protected $description = 'Create a backend admin user';
/**
* Execute the console command.
@ -29,20 +31,78 @@ class CreateAdmin extends Command
*/
public function handle()
{
$firstname = $this->ask('What is the firstname?', 'Equipe');
$lastname = $this->ask('What is the lastname?', 'Web');
$email = $this->ask('What is the email?', 'equipe.web@ldc.fr');
$pwd = $this->secret('What is the password?');
$email = null;
while (is_null($email)) {
try {
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
$validator = Validator::make(['email' => $tmp], [
'email' => 'required|unique:users,email|email:rfc,spoof'
]);
$validator->validate();
$email = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
$firstname = null;
while (is_null($firstname)) {
try {
$tmp = $this->ask('Type the wanted user firstname', 'Admin');
$validator = Validator::make(['firstname' => $tmp], [
'firstname' => 'required|min:5,max:255'
]);
$validator->validate();
$firstname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid firstname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
$lastname = null;
while (is_null($lastname)) {
try {
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama');
$validator = Validator::make(['lastname' => $tmp], [
'lastname' => 'required|min:5,max:255'
]);
$validator->validate();
$lastname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid lastname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
$password = null;
while (
($password = $this->secret(
(!\is_null($password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
$user = new User();
$user->firstname = $firstname;
$user->lastname = $lastname;
$user->date_of_birth = now();
$user->email = $email;
$user->password = Hash::make($pwd);
$user->password = $password;
$user->role = $this->confirm('Is the user an admin?') ?
UserRole::admin() : UserRole::user();
$user->email_verified_at = now();
$user->role = 1;
$user->save();
$user->saveOrFail();
$this->info('User created.');
}

View file

@ -1,37 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Jobs\CsvSalariesReader as JobCsvSalarieReader;
class ImportSalaries extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'import:salaries';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import Salaries, pour provision des clients';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->info('Running the Job, this could take some time.');
$job = new JobCsvSalarieReader();
$job->console = $this;
$job->handle();
$this->info('Job completed');
}
}

View file

@ -1,76 +0,0 @@
<?php
/**
* Command Name: LDC Web Status Checker
* Description: Envoi des infos Serveur/Laravel vers LDC Web
* Version: 1.0
* Author: Vincent BRIDIER
* Author URI: vincent.bridier@ldc.fr
*/
namespace App\Console\Commands;
use Illuminate\Console\Command;
class LDCWebStatusChecker extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ldcweb:statuschecker';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Envoi des infos Serveur/Laravel vers LDC Web';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$data = [];
$data['uuid'] = config('ldcweb.project_uuid');
// Sitemap.xml ?
if (file_exists(public_path() . '/sitemap.xml')) {
$data['sitemap'] = 'true';
} else {
$data['sitemap'] = 'false';
}
// Robots.txt ?
if (file_exists(public_path() . '/robots.txt')) {
$data['robots'] = 'true';
} else {
$data['robots'] = 'false';
}
// Check logs.
$data['logs'] = 0;
$patherror = storage_path('/logs');
$files = glob($patherror . '/*.log');
if (count($files) > 0) {
foreach ($files as $file) {
$data['logs'] = $data['logs'] + filesize($file);
}
}
$data['code']['version'] = app()->version();
// Send by cURL.
$url = 'https://ldc-web.ldc.fr/api/status-checker';
$ch = curl_init($url);
$payload = ['token' => 'CE8S52Qq7stf5k8frF4T', 'datas' => json_encode($data)];
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
}
}

View file

@ -1,37 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Jobs\RefreshSiteMap as JobsRefreshSiteMap;
use Illuminate\Console\Command;
class RefreshSiteMap extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sitemap:refresh';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the RefreshSiteMapJob manually';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->info('Running the Job, this could take some time.');
$job = new JobsRefreshSiteMap();
$job->console = $this;
$job->handle();
$this->info('Job completed');
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use App\Enums\GeneralConfigType;
use App\Models\Site\GeneralConfig;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class SyncThemeSettings extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'config:sync_theme';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Synchronize theme config with database.';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
// * Force config clearance.
Artisan::call('config:clear');
// * Force cache to be cleared ( reload GeneralConfigs )
Artisan::call('cache:clear');
// * Fetch or create theme config
/** @var \App\Models\Site\GeneralConfig */
$config = GeneralConfig::query()->where('type', GeneralConfigType::theme())->firstOrNew();
// * Fetch Bo Theme Settings.
$data = config('theme.bo_theme_settings');
$config->data = $data;
$config->saveOrFail();
$this->info('Theme config is overrided with the latest theme.config.php content.');
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace App\Console\Commands;
use App\Jobs\Payment\SystempayCreditTransactionUpdate;
use Illuminate\Console\Command;
class SystempayCreditTransactionUpdateCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'systempay:update_credit_transactions';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Update Systempay credit transaction statuses';
/**
* Execute the console command.
*
* @return inteager
*/
public function handle()
{
\dispatch_sync(new SystempayCreditTransactionUpdate());
return 0;
}
}

View file

@ -2,6 +2,15 @@
namespace App\Console;
use App\Jobs\DeleteOlds\DeleteOldCollectionException;
use App\Jobs\DeleteOlds\DeleteOldCustomers;
use App\Jobs\DeleteOlds\DeleteOldOrders;
use App\Jobs\DeleteOlds\DeleteOldTimeslots;
use App\Jobs\DiscountsStatusUpdate;
use App\Jobs\Payment\SystempayCreditTransactionUpdate;
use App\Jobs\ProductsOrderableUpdate;
use App\Jobs\ProductsStatusUpdate;
use App\Jobs\SlidesStatusUpdate;
use App\Jobs\XmlIncoReader as JobsXmlIncoReader;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -13,8 +22,7 @@ class Kernel extends ConsoleKernel
*
* @var array
*/
protected $commands = [
];
protected $commands = [];
/**
* Define the application's command schedule.
@ -24,9 +32,21 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
// * Update models planified booleans changes
$schedule->job(SlidesStatusUpdate::class)->everyMinute();
$schedule->job(ProductsStatusUpdate::class)->everyMinute();
$schedule->job(ProductsOrderableUpdate::class)->everyMinute();
$schedule->job(DiscountsStatusUpdate::class)->everyMinute();
$schedule->job(DeleteOldTimeslots::class)->everyMinute();
$schedule->command('model:prune')->everyMinute();
$schedule->command('sitemap:refresh')->dailyAt('16:13');
$schedule->command('ldcweb:statuschecker')->everyFifteenMinutes();
$schedule->job(JobsXmlIncoReader::class)->name('xml-inco-reader')->onOneServer()->dailyAt('15:00');
$schedule->job(DeleteOldCustomers::class)->dailyAt('22:50');
$schedule->job(DeleteOldOrders::class)->dailyAt('23:00');
$schedule->job(DeleteOldCollectionException::class)->dailyAt('10:42');
$schedule->job(SystempayCreditTransactionUpdate::class)->dailyAt('03:00');
}
/**

View file

@ -0,0 +1,79 @@
<?php
namespace App\Enums\Discount;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self amount()
* @method static self percent()
*/
final class DiscountType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'amount' => 0,
'percent' => 1,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'amount' => trans('Montant'),
'percent' => trans('Pourcentage'),
];
}
/**
* Enum units
*
* @return array<float>
*/
protected static function units(): array
{
return [
'amount' => '€',
'percent' => '%'
];
}
/**
* Enum steps
*
* @return array<float>
*/
protected static function steps(): array
{
return [
'amount' => 0.01,
'percent' => 1
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'unit') {
return $this->units()[$this->getDefinition()] ?? null;
}
if ($attribute === 'step') {
return $this->steps()[$this->getDefinition()] ?? null;
}
return parent::__get($attribute);
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self global()
* @method static self orders()
* @method static self theme()
*/
final class GeneralConfigType extends BaseEnumRoutable
{
/**
* Values
*
* @return array
*/
protected static function values(): array
{
return [
'global' => 1,
'orders' => 2,
'theme' => 3,
];
}
/**
* Labels
*
* @return array
*/
protected static function labels(): array
{
return [
'global' => __('Configuration du site'),
'orders' => __('Configuration des commandes'),
'theme' => __('Configuration du theme'),
];
}
}

50
app/Enums/InputType.php Normal file
View file

@ -0,0 +1,50 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self boolean()
* @method static self image()
* @method static self string()
* @method static self password()
* @method static self text()
* html autorisé
* @method static self url()
* @method static self imageArray()
* @method static self labelArray()
* @method static self dropdownArray()
* html autorisé
* @method static self miniblocArray()
* html autorisé
* @method static self videoVimeo()
* @method static self number()
* @method static self email()
*/
final class InputType extends BaseEnumRoutable
{
/**
* Values
*
* @return array
*/
protected static function values(): array
{
return [
'boolean' => 0,
'image' => 1,
'string' => 2,
'text' => 3,
'url' => 4,
'imageArray' => 5,
'labelArray' => 6,
'dropdownArray' => 7,
'miniblocArray' => 8,
'videoVimeo' => 9,
'number' => 10,
'email' => 11,
'password' => 12
];
}
}

View file

@ -1,50 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self pce()
* @method static self kgm()
* @method static self colis()
*/
final class InvoicingUnit extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pce' => 'PCE',
'kgm' => 'KGM',
'colis' => 'COLIS',
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pce' => __('Pièce'),
'kgm' => __('Kg'),
'colis' => __('Colis'),
];
}
/**
* Output the value as a string
* @return string
*/
public function __toString(): string
{
return (string)$this->label;
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self inShop()
*/
final class DeliveryModeEnum extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'inShop' => 1
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'inShop' => __('À la boutique')
];
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self pending()
* Aucune action n'a été entreprise.
* @method static self preparation()
* La commande est en préparation
* @method static self ready()
* La commande est prête a être récupérée
* @method static self recovered()
* La commande à été récupérée par le client
* @method static self canceled()
* La commande à été annulée
*/
final class DeliveryStatus extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pending' => 0,
'preparation' => 1,
'ready' => 2,
'recovered' => 3,
'canceled' => 4
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pending' => __('À faire'),
'preparation' => __('En préparation'),
'ready' => __('Prête à être récupérée'),
'recovered' => __('Récupérée'),
'canceled' => __('Annulée')
];
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self reviewBasket()
* @method static self shippingMethod()
* @method static self shippment()
* @method static self paymentReview()
*/
final class OrderProcess extends BaseEnumRoutable
{
/**
* Values
*
* @return array
*/
protected static function values(): array
{
return [
'reviewBasket' => 0,
'shippingMethod' => 1,
'shippment' => 2,
'paymentReview' => 3
];
}
/**
* Labels
*
* @return array
*/
protected static function labels(): array
{
return [
'reviewBasket' => trans('Panier'),
'shippingMethod' => trans('Expédition - mode'),
'shippment' => trans('Expédition - détails'),
'paymentReview' => trans('Paiement')
];
}
}

View file

@ -0,0 +1,117 @@
<?php
namespace App\Enums\Order;
use App\Lib\Payments\Adapters\InShopPaymentAdapter;
use App\Lib\Payments\Adapters\StripePaymentAdapter;
use App\Lib\Payments\Adapters\SystemPayPaymentAdapter;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self inShop()
* Payment in the selected shop when retriving the order
* @method static self stripe()
* Payment using Stripe services
* @method static self systemPay()
* Payment using SystemPay services
*
* @property-read string $adapter The adpater (\App\Lib\Payments\PaymentAdapter) to use for the payment mode.
* @property-read integer $minAmount The minimal amount to use the payment mode in cents.
* @property-read string $endpoint The host endpoint for the payment mode.
*/
final class PaymentModeEnum extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'inShop' => 1,
'stripe' => 2,
'systemPay' => 3
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'inShop' => __('Au magasin'),
'stripe' => __('Stripe'),
'systemPay' => __('SystemPay')
];
}
/**
* Enum rates
*
* @return array<\App\Lib\Payments\PaymentAdapter>
*/
protected static function adapterClasses(): array
{
return [
'inShop' => InShopPaymentAdapter::class,
'stripe' => StripePaymentAdapter::class,
'systemPay' => SystemPayPaymentAdapter::class,
];
}
/**
* Enum minimal amounts in cents
*
* TODO: Set min amounts (and max) in settings.
*
* @return array<\App\Lib\Payments\PaymentAdapter>
*/
protected static function minAmount(): array
{
return [
'inShop' => 0,
// * 50 cents
'stripe' => 50,
// * 1 euro
'systemPay' => 100,
];
}
/**
* Enum payment modes endpoints
*
* This is to allow in cors in App Service Provider
*
* @return array<\App\Lib\Payments\PaymentAdapter>
*/
protected static function endpoints(): array
{
return [
'inShop' => \parse_url(\config('app.url'), \PHP_URL_HOST),
'stripe' => 'stripe.fr',
'systemPay' => 'systempay.fr'
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'adapter') {
return $this->adapterClasses()[$this->getDefinition()];
}
if ($attribute === 'minAmount') {
return $this->minAmount()[$this->getDefinition()];
}
if ($attribute === 'endpoint') {
return $this->endpoints()[$this->getDefinition()];
}
return parent::__get($attribute);
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self pending()
* Payment en attente
* @method static self failed()
* Payment à échoué
* @method static self payed()
* Le règlement à bien été validé
* @method static self refundedPartial()
* Le règlement à été remboursé partiellement
* @method static self refunded()
* Le règlement à été remboursé complétement
* @method static self canceled()
* Le règlement à été annulé
*/
final class PaymentStatus extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pending' => 0,
'failed' => 1,
'payed' => 3,
'refundedPartial' => 4,
'refunded' => 5,
'canceled' => 6
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pending' => __('En attente'),
'failed' => __('Échec'),
'payed' => __('Réglée'),
'refundedPartial' => __('Remboursé partiellement'),
'refunded' => __('Remboursé complètement'),
'canceled' => __('Annulée'),
];
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self pending()
* Transaction en cours
* @method static self failed()
* Transaction à échoué
* @method static self validated()
* Transaction est validée
* @method static self canceled()
* Transaction est anluléee
*/
final class TransactionStatus extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pending' => 0,
'failed' => 1,
'validated' => 2,
'canceled' => 3
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pending' => __('En attente'),
'failed' => __('Échec'),
'validated' => __('Validée'),
'canceled' => __('Annulée')
];
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self debit()
* Mouvement d'une transaction du client vers la boutique
* @method static self credit()
* Mouvement d'une transaction de la boutique vers le client
*/
final class TransactionType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'debit' => 0,
'credit' => 1
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'debit' => __('Débit'),
'credit' => __('Crédit')
];
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self pending()
* La commande n'est pas encore évaluée
* @method static self canceled()
* La commande est annulée
* @method static self validated()
* La commande est validée (pour préparation)
*/
final class ValidationStatus extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pending' => 0,
'canceled' => 1,
'validated' => 2,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pending' => __('En attente de validation'),
'canceled' => __('Annulée'),
'validated' => __('Validée'),
];
}
}

View file

@ -1,50 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self pce()
* @method static self kgm()
* @method static self colis()
*/
final class OrderUnit extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pce' => 'PCE',
'kgm' => 'KGM',
'colis' => 'COLIS',
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pce' => __('Pièce'),
'kgm' => __('Kg'),
'colis' => __('Colis'),
];
}
/**
* Output the value as a string
* @return string
*/
public function __toString(): string
{
return (string)$this->label;
}
}

View file

@ -0,0 +1,80 @@
<?php
namespace App\Enums\Product;
use App\Enums\InputType;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self default()
* @method static self inco()
*/
final class DefaultProductType extends BaseEnumRoutable
{
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'default' => 'De base',
'inco' => 'Alimentaire lié a Inco',
];
}
/**
* Get Inco minicrud inputs for specific product type inco
* ! Attention toute modification après la mise en
* ! production doit faire l'objet d'une migration
* ! pour réaligner le contenu du champ data
* ! pour chaque type de produit
* ! sous peine de de tout casser
*
* @return array
*/
public static function incoInputs(): array
{
return [
'ingredients' => [
'label' => 'Ingrédients',
'inputType' => InputType::text(),
'text' => '',
'validate' => 'nullable|string'
],
'preparation_advices' => [
'label' => 'Conseils de préparation',
'inputType' => InputType::text(),
'text' => '',
'validate' => 'nullable|string'
],
'allergens' => [
'label' => 'Ingrédients allèrgenes',
'inputType' => InputType::text(),
'text' => '',
'validate' => 'nullable|string'
],
'selling_arguments' => [
'label' => 'Arguments de vente',
'inputType' => InputType::text(),
'text' => '',
'validate' => 'nullable|string'
],
'servings' => [
'label' => 'Portions',
'inputType' => InputType::string(),
'value' => '',
'required' => false,
'validate' => 'nullable|string|max:35'
],
'servings_type' => [
'label' => 'Type de portions',
'inputType' => InputType::string(),
'value' => '',
'required' => false,
'validate' => 'nullable|string|max:150'
],
];
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace App\Enums;
namespace App\Enums\Product;
use Kwaadpepper\Enum\BaseEnum;

View file

@ -0,0 +1,71 @@
<?php
namespace App\Enums\Product;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* Calcul par unité de produits
*
* @method static self twenty()
* @method static self ten()
* @method static self five()
*
* @property-read float $rate The TVA rate.
*/
final class TaxRate extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'twenty' => 0,
'ten' => 1,
'five' => 2
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'twenty' => trans('Taux normal'),
'ten' => trans('Taux reduit'),
'five' => trans('Taux très reduit')
];
}
/**
* Enum rates
*
* @return array<float>
*/
protected static function rates(): array
{
return [
'twenty' => 20,
'ten' => 10,
'five' => 5.5,
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'rate') {
return $this->rates()[$this->getDefinition()] ?? null;
}
return parent::__get($attribute);
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace App\Enums\Product;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self g()
* @method static self kg()
* @method static self ton()
*/
final class WeightUnit extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'g' => 'G',
'kg' => 'KG',
'ton' => 'T',
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'g' => __('g'),
'kg' => __('kg'),
'ton' => __('t'),
];
}
/**
* Enum conversions a partir de gramme
*
* @return array
*/
protected static function conversions(): array
{
return [
'g' => 1,
'kg' => 1000,
'ton' => 1000000,
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'conversion') {
return $this->conversions()[$this->getDefinition()] ?? null;
}
return parent::__get($attribute);
}
/**
* Format weight into a displayable unit.
* @param integer $weightG The weight in grams.
* @param \App\Enums\WeightUnit|null $forceUnit To force specific unit display.
* @return string
*/
public static function getDisplayableWeight(int $weightG, self $forceUnit = null): string
{
if ($forceUnit) {
number_format(round($weightG / $forceUnit->conversion, 2), 2, ',', '') . $forceUnit->label;
}
$displayable = self::convertWeightToAdequatUnit($weightG);
return \number_format($displayable->value, 2, ',', '') . ' ' . $displayable->enum->label;
}
/**
* Convert value to the adequat unit
* @param integer $weightG The weight in grams.
* @return object<'enum'|'value',self>
*/
private static function convertWeightToAdequatUnit(int $weightG): object
{
foreach (self::conversions() as $key => $conversionRate) {
$convertedTest = round($weightG / $conversionRate, 2);
if ($convertedTest > 0.001 and $convertedTest <= 1000) {
return (object)[
'enum' => self::make($key),
'value' => $convertedTest
];
}
}
return (object)[
'enum' => \collect(self::toArray())->last(),
'value' => $weightG
];
}
}

View file

@ -1,47 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self public()
* @method static self salaries()
*/
final class SettingRegisterMode extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'public' => 'public',
'salaries' => 'salaries',
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'public' => __('Public'),
'salaries' => __('Salaries'),
];
}
/**
* Output the value as a string
* @return string
*/
public function __toString(): string
{
return (string)$this->label;
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self home()
* Page d'accueil
* @method static self contact()
* Page de contact
* @method static self legals()
* Page mentions légales
* @method static self sellconditions()
* Page conditions de ventes
* @method static self confidentiality()
* Page politique de confidentialitée
* @method static self faq()
* Page faq
* @method static self crud()
* Page de contenus libre en CRUD.
*/
final class StaticPageType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'home' => 0,
'contact' => 1,
'legals' => 2,
'sellconditions' => 3,
'confidentiality' => 4,
'faq' => 5,
'crud' => 6
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'home' => trans('Accueil'),
'contact' => trans('Contact'),
'legals' => trans('Mentions Légales'),
'sellconditions' => trans('CGV'),
'confidentiality' => trans('Politique de confidentialité'),
'faq' => trans('FAQ'),
'crud' => trans('Pages libres'),
];
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace App\Enums\Stripe;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self paid()
* @method static self unpaid()
* @method static self no_payment_required()
*/
final class CheckoutPaymentStatus extends BaseEnum
{
}

View file

@ -0,0 +1,53 @@
<?php
namespace App\Enums\TimeSlot;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self monday()
* @method static self tuesday()
* @method static self wednesday()
* @method static self thursday()
* @method static self friday()
* @method static self saturday()
* @method static self sunday()
*/
final class WeekDay extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'monday' => 1,
'tuesday' => 2,
'wednesday' => 3,
'thursday' => 4,
'friday' => 5,
'saturday' => 6,
'sunday' => 7,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'monday' => trans('Lundi'),
'tuesday' => trans('Mardi'),
'wednesday' => trans('Mercredi'),
'thursday' => trans('Jeudi'),
'friday' => trans('Vendredi'),
'saturday' => trans('Samedi'),
'sunday' => trans('Dimanche')
];
}
}

View file

@ -5,14 +5,10 @@ namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* This is an enumeration class,
* the following methods are its possible values
*
* @method static self authenticated()
* @method static self guest()
*
* @method static self admin()
* @method static self user()
*/
class PriceShowingMode extends BaseEnumRoutable
final class UserRole extends BaseEnumRoutable
{
/**
* Enum values
@ -22,8 +18,8 @@ class PriceShowingMode extends BaseEnumRoutable
protected static function values(): array
{
return [
'authenticated' => 'authenticated',
'guest' => 'guest',
'admin' => 1,
'user' => 0,
];
}
@ -35,8 +31,8 @@ class PriceShowingMode extends BaseEnumRoutable
protected static function labels(): array
{
return [
'authenticated' => __('Client Connecté'),
'guest' => __('Public'),
'admin' => trans('Administrateur'),
'user' => trans('Utilisateur'),
];
}
}

View file

@ -1,65 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self g()
* @method static self kg()
* @method static self ton()
*/
final class WeightUnit extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'g' => 'G',
'kg' => 'KG',
'ton' => 'T',
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'g' => __('Gramme'),
'kg' => __('Kg'),
'ton' => __('Tonne'),
];
}
/**
* conversion values
* base kg()
*
* @return array
*/
public static function convert(): array
{
return [
'G' => 0.001,
'KG' => 1,
'T' => 1000,
];
}
/**
* Output the value as a string
* @return string
*/
public function __toString(): string
{
return (string)$this->label;
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace App\Events;
use App\Models\Shop\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderIsCreated
{
use Dispatchable;
use SerializesModels;
/** @var \App\Models\Shop\Order */
private $order;
/**
* Create a new event instance.
*
* @param \App\Models\Shop\Order $order
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
/**
* Get Event order
*
* @return \App\Models\Shop\Order
*/
public function getOrder(): Order
{
return $this->order;
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace App\Events;
use App\Models\Payment\Transaction;
use App\Models\Shop\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderIsPaid
{
use Dispatchable;
use SerializesModels;
/** @var \App\Models\Shop\Order */
private $order;
/** @var \App\Models\Payment\Transaction */
private $transaction;
/**
* Create a new event instance.
*
* @param \App\Models\Shop\Order $order
* @param \App\Models\Payment\Transaction $transaction
* @return void
*/
public function __construct(Order $order, Transaction $transaction)
{
$this->order = $order;
$this->transaction = $transaction;
}
/**
* Get Event order
*
* @return \App\Models\Shop\Order
*/
public function getOrder(): Order
{
return $this->order;
}
/**
* Get Event transaction
*
* @return \App\Models\Payment\Transaction
*/
public function getTransaction(): Transaction
{
return $this->transaction;
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace App\Events;
use App\Models\Payment\Transaction;
use App\Models\Shop\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderIsRefunded
{
use Dispatchable;
use SerializesModels;
/** @var \App\Models\Shop\Order */
private $order;
/** @var \App\Models\Payment\Transaction */
private $transaction;
/**
* Create a new event instance.
*
* @param \App\Models\Shop\Order $order The order to refund.
* @param \App\Models\Payment\Transaction $transaction The transaction concerned by the refund.
* @return void
*/
public function __construct(Order $order, Transaction $transaction)
{
$this->order = $order;
$this->transaction = $transaction;
}
/**
* Get Event order
*
* @return \App\Models\Shop\Order
*/
public function getOrder(): Order
{
return $this->order;
}
/**
* Get Event transaction
*
* @return \App\Models\Payment\Transaction
*/
public function getTransaction(): Transaction
{
return $this->transaction;
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace App\Events;
use App\Models\Shop\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderIsValidated
{
use Dispatchable;
use SerializesModels;
/** @var \App\Models\Shop\Order */
private $order;
/**
* Create a new event instance.
*
* @param \App\Models\Shop\Order $order
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
/**
* Get Event order
*
* @return \App\Models\Shop\Order
*/
public function getOrder(): Order
{
return $this->order;
}
}

View file

@ -3,23 +3,20 @@
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Support\Facades\Mail;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
* @var array<int,class-string<Throwable>>
*/
protected $dontReport = [];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
* @var array<int,string>
*/
protected $dontFlash = [
'password',
@ -33,65 +30,10 @@ class Handler extends ExceptionHandler
*/
public function register()
{
}
/**
* Handle unauthenticated as exception
*
* @param mixed $request
* @param AuthenticationException $exception
* @return \Illuminate\Http\RedirectResponse
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
$guard = $exception->guards()[0];
switch ($guard) {
case 'customer':
$login = 'auth.connexion';
break;
default:
$login = 'auth.login';
break;
}
// Cf. issue :
// Https://stackoverflow.com/questions/52417438/laravel-unauthenticated-redirect-issue-with-multiple-authentication.
// Session::forget('url.intented'); .
return redirect()->guest(route($login));
}
/**
* Report or log an exception.
*
* @param \Throwable $e
* @return void
*/
public function report(Throwable $e)
{
if (get_class($e) == RedirectTo::class) {
abort(redirect($e->url));
}
if (config('app.debug') === false) {
if ($this->shouldntReport($e)) {
return;
$this->reportable(function (\Throwable $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
Mail::send([
'html' => 'emails.exception',
'text' => 'emails.exception-plain'
], [
'exception' => $e,
'appName' => config('app.name')
], function ($m) {
$m->to(
config('mail.contactsList.serviceweb'),
sprintf('%s Exception Handler', config('app.name'))
)->subject('Need help !, an error occured in your application, please check Logs ASAP');
});
}
parent::report($e);
});
}
}

View file

@ -1,70 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* Display login form
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function index(Request $request)
{
// @phpcs:enable
return view('auth.login-bo');
}
/**
* Compute login form
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
*/
public function login(Request $request)
{
$request->validate([
'email' => 'required',
'password' => 'required'
]);
$credentials = $request->only(['email', 'password']);
if (Auth::attempt($credentials)) {
$user = Auth::user();
if ($user->role === 1 || $user->role === 2) {
return redirect()->route('bo.dashboard');
} else {
if ($user->email_verified_at === null) {
Auth::logout();
return view('auth.login')->withErrors('Compte non activé.');
}
return redirect()->intended('/');
}
}
return view('auth.login')->withErrors('Informations incorrectes.');
}
/**
* logout
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function logout(Request $request)
{
// @phpcs:enable
Auth::logout();
return redirect()->route('home');
}
}

View file

@ -1,56 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class RegisterController extends Controller
{
/**
* Display register form
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function index(Request $request)
{
// @phpcs:enable
return view('auth.register');
}
/**
* Compute regioster form
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*
*/
public function register(Request $request)
{
$request->validate([
'lastname' => 'required|string|max:255',
'firstname' => 'required|string|max:255',
'date_of_birth' => 'required|date|before_or_equal:' . Carbon::now()->subYears(18)->toDateString(),
'email' => 'required|string|email|max:255|unique:users|confirmed',
'password' => 'required|string|min:8',
]);
User::create([
'firstname' => $request->firstname,
'lastname' => $request->lastname,
'date_of_birth' => $request->date_of_birth,
'email' => $request->email,
'password' => Hash::make($request->password),
'role' => 3
]);
// Envoi mail activation.
return redirect()->route('auth.register')->with('success', 'Mail envoyé pour confirmation');
}
}

View file

@ -1,23 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ResetPasswordController extends Controller
{
/**
* Display reset password form
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function index(Request $request)
{
// @phpcs:enable
return view('auth.reset-password');
}
}

View file

@ -1,164 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Support\Facades\Auth;
class BasketController extends Controller
{
/**
* ajout de produit au panier
*
* @param \Illuminate\Http\Request $request
*
* @return array
*/
public function addToCart(Request $request)
{
$instance = $request->instance ?? "panier";
$product = Product::find($request->id);
Cart::instance($instance);
Cart::add($product, $request->qty);
return $this->getCart($instance);
}
/**
* ajout de plusieurs produits au panier
*
* @param \Illuminate\Http\Request $request
*
* @return array
*/
public function addToCartLot(Request $request)
{
$instance = $request->instance ?? "panier";
Cart::instance($instance);
$lot = $request->items;
foreach ($lot as $item) {
$product = Product::find($item["cartItem"]["id"]);
Cart::add($product, $item["cartItem"]["qty"]);
}
return $this->getCart($instance);
}
/**
* suppression de produit du panier
*
* @param \Illuminate\Http\Request $request
*
* @return array
*/
public function removeFromCart(Request $request)
{
$instance = $request->instance ?? "panier";
$rowId = $request->rowId;
Cart::instance($instance);
Cart::remove($rowId);
return $this->getCart($instance);
}
/**
* vidage de panier
*
* @param \Illuminate\Http\Request $request
*
* @return array
*/
public function emptyCart(Request $request)
{
$instance = $request->instance ?? "panier";
Cart::instance($instance);
Cart::destroy();
return $this->getCart($instance);
}
/**
* récupération du panier
*
* @param \Illuminate\Http\Request $request
*
* @return array
*/
public function getCartRequest(Request $request)
{
$instance = $request->instance ?? "panier";
Cart::instance($instance);
return $this->getCart($instance);
}
/**
* return cart data array
*
* @param string $instance
* @return array
*/
public function getCart(string $instance = "panier")
{
Cart::instance($instance);
$dbpanier = new Collection();
foreach (Cart::content() as $row) {
$product = $row->model;
$product->calculatedPrices = $product->getCalcPrices();
$row->setTaxRate($product->tax->rate);
$row->setDiscountRate($product->discountRate);
$cartextendedItem = array(
'cartItem' => $row,
'product' => $product
);
$dbpanier->add($cartextendedItem);
}
$cartInfos = array(
"totalTTC" => Cart::total(),
// Total des taxes.
'totalTVA' => Cart::tax(),
// Total sans les taxes.
'totalHT' => Cart::subtotal(),
// Poids total des items.
'totalweight' => Cart::weight(),
// Total avant taxes et remises.
'totalHTbefore' => Cart::priceTotal(),
// Total des remises.
'totaldiscount' => Cart::discount(),
// Nb de produits dans le cart.
'countItems' => Cart::count(),
);
$cartInfosNoPrice = array(
"totalTTC" => "-.--",
// Total des taxes.
'totalTVA' => "-.--",
// Total sans les taxes.
'totalHT' => "-.--",
// Poids total des items.
'totalweight' => Cart::weight(),
// Total avant taxes et remises.
'totalHTbefore' => "-.--",
// Total des remises.
'totaldiscount' => "-.--",
// Nb de produits dans le cart.
'countItems' => Cart::count(),
);
$cartReturn = array($dbpanier,$cartInfosNoPrice);
if (config('bosettings.show_prices_authenticated') == "authenticated") {
{
if (Auth::guard('customer')->check()) {
$cartReturn = array($dbpanier,$cartInfos);
}
}
}
if (config('bosettings.show_prices_guest') == "guest") {
$cartReturn = array($dbpanier,$cartInfos);
}
return $cartReturn;
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:backend')->except('logout');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('backend');
}
/**
* Show the application's login form.
*
* @return \Illuminate\View\View
*/
public function showLoginForm()
{
return view('back::auth.login');
}
/**
* Where to redirect users after login.
* @return string
*/
protected function redirectTo(): string
{
$intendedUrl = response()->redirectToIntended()->getTargetUrl();
$intendedRequest = Request::create($intendedUrl);
return \strpos($intendedRequest->path(), '/ bo') === 0 ?
response()->redirectToIntended()->getTargetUrl() :
\route('bo.dashboard');
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Auth;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('backend');
}
/**
* Where to redirect users after login.
* @return string
*/
protected function redirectTo(): string
{
return \route('bo.login');
}
}

View file

@ -0,0 +1,93 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Customer\Contact;
use Illuminate\Http\Request;
class ContactMessagesController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $contactMessages */
$contactMessages = Contact::query();
if ($request->search) {
$this->searchQuery(
$contactMessages,
$request->search,
null,
'order_number',
'firstname',
'lastname',
'subject',
'email',
);
}
$this->sortQuery($contactMessages);
$searchFields = trans('N° cmde, Nom, Prénom, Objet, E-mail');
$contactMessages = $contactMessages->orderBy('done', 'ASC')
->orderBy('created_at', 'DESC')->paginate();
return view('back::pages.contacts.index', compact('contactMessages', 'searchFields'));
}
/**
* Show contact message
*
* @param Contact $contact
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function show(Contact $contact)
{
return view('back::pages.contacts.show', [
'contactMessage' => $contact
]);
}
/**
* Show contact message
*
* @param Contact $contact
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function done(Contact $contact)
{
$contact->done = true;
if ($contact->saveOrFail()) {
return response()->redirectToRoute('bo.contacts.index')->with(
'bo.success',
trans('Le message à été marqué comme traité')
);
}
return back();
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Customer\Contact $contact
* @return \Illuminate\Http\Response
*/
public function destroy(Contact $contact)
{
if ($contact->deleteOrFail()) {
return redirect()
->route('bo.contacts.index')
->with('bo.warning', trans(
':model a bien été supprimé',
['model' => __('Message de contact')]
));
}
return back();
}
}

View file

@ -1,56 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Content;
use Illuminate\Http\Request;
class ContentController extends Controller
{
/**
* Manage content page
*
* @param Request $request
* @param string $key
* @return \Illuminate\Http\Response
*
*/
public function manage(Request $request, string $key)
{
$content = Content::where('key', $key)->first();
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$banner = $request->banner;
$title = $request->title;
$chapeau = $request->chapeau;
$maintext = $request->value;
$request->validate([
'banner' => 'image|mimes:jpeg,png,jpg|max:2048|dimensions:max_width=' .
config('theme.content_banner_maxwidth') .
',max_height=' . config('theme.content_banner_maxheight'),
'value' => 'required',
]);
if ($banner !== null) {
if ($content->banner !== null && file_exists(public_path('images/content/') . $content->banner)) {
unlink(public_path('images/content/') . $content->banner);
}
$imageName = $content->id . '.' . $banner->extension();
$banner->move(public_path('images/content'), $imageName);
$content->banner = $imageName;
}
$content->title = $title;
$content->chapeau = $chapeau;
$content->value = $maintext;
$content->save();
return redirect()->route('bo.content', ['key' => $content->key, 'content' => $content])
->with('success', 'Modifications enregistrés !');
}//end if
return view('bo.content.manage', compact('content'));
}
}

View file

@ -1,80 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Magasin;
use App\Models\TimeslotSetting;
use Illuminate\Http\Request;
class CreneauxController extends Controller
{
/**
* Display creneaux settings form
*
* @param Request $request
* @param integer $id
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function settings(Request $request, int $id)
{
// @phpcs:enable
$magasin = Magasin::findOrFail($id);
$timeslotSettings = $magasin->timeslotSettings;
if ($timeslotSettings->count() === 0) {
// Aucun settings pour ce magasin.
$timeslotSettings = $this->makeCreneauxSettings($id);
}
return view('bo.creneaux.settings', compact('timeslotSettings', 'magasin'));
}
/**
* Retrieve creneaux settings
*
* @param integer $id
* @return mixed
*/
public function makeCreneauxSettings(int $id)
{
for ($i = 0; $i < 7; $i++) {
$dayset = new TimeslotSetting();
$dayset->magasin_id = $id;
$dayset->day_index = '' . $i;
$dayset->save();
}
return Magasin::findOrFail($id)->timeslotSettings;
}
/**
* save settings
*
* @param Request $request
* @param integer $id
* @return \Illuminate\Http\RedirectResponse
*/
public function updateSettings(Request $request, int $id)
{
$plage = $request->plage;
$magasin_id = $id;
foreach ($plage as $index => $day) {
$setting = TimeslotSetting::where('day_index', '' . $index)
->where('magasin_id', $magasin_id)
->firstOrFail();
// $setting->magasin_id = $magasin_id; ne doit pas changer
$setting->morning_start = $day['morning_start'];
$setting->morning_end = $day['morning_end'];
$setting->afternoon_start = $day['afternoon_start'];
$setting->afternoon_end = $day['afternoon_end'];
$setting->interval = $day['interval'];
$setting->max_orders_per_interval = $day['max_orders_per_interval'];
$setting->min_order_preparation = $day['min_order_preparation'];
$setting->save();
}
return redirect()->route('bo.creneaux.settings', $magasin_id)->with('success', 'Modifications enregistrées !');
}
}

View file

@ -13,6 +13,6 @@ class DashboardController extends Controller
*/
public function index()
{
return view('bo.dashboard');
return view('back::pages.dashboard');
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\DeliveryMode\UpdateDeliveryModeRequest;
use App\Models\Shop\DeliveryMode;
use Illuminate\Support\Str;
class DeliveryModeController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$deliveryModes = DeliveryMode::paginate();
return view('back::pages.delivery-modes.index', compact('deliveryModes'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Shop\DeliveryMode $deliveryMode
* @return \Illuminate\Http\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function edit(DeliveryMode $deliveryMode)
{
$this->authorize('update', DeliveryMode::class);
return view('back::pages.delivery-modes.edit', compact(
'deliveryMode'
));
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\UpdateDeliveryModeRequest $request
* @param \App\Models\Shop\DeliveryMode $deliveryMode
* @return \Illuminate\Http\Response
*/
public function update(UpdateDeliveryModeRequest $request, DeliveryMode $deliveryMode)
{
$this->authorize('update', DeliveryMode::class);
$deliveryMode->fill($request->validated());
if ($deliveryMode->saveOrFail()) {
return redirect()->back()
->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.delivery_modes'))]
));
}
return back();
}
/**
* Change model status
*
* @param DeliveryMode $model
* @return \Illuminate\Http\RedirectResponse
*/
public function changeStatus(DeliveryMode $model)
{
$this->authorize('update', DeliveryMode::class);
$model->status = !$model->status;
$model->saveOrFail();
return redirect()->back();
}
}

View file

@ -0,0 +1,173 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\Discount\DiscountType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Discounts\StoreDiscountRequest;
use App\Http\Requests\Bo\Discounts\UpdateDiscountRequest;
use App\Models\Shop\Discount;
use App\Models\Merchandise\Product;
use App\Traits\Controllers\UpdateModelStatus;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class DiscountController extends Controller
{
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $discounts */
$discounts = Discount::query();
if ($request->search) {
$this->searchQuery(
$discounts,
$request->search,
// * Search in ranges
function (Builder $q) use ($request) {
$types = \collect(DiscountType::toArray())->filter(function (DiscountType $type) use ($request) {
$test = Str::contains(
Str::lower($type->label),
\explode(' ', Str::lower(\trim($request->search)))
);
return Str::contains(
Str::lower($type->label),
\explode(' ', Str::lower(\trim($request->search)))
);
});
$q->orWhereIn('type', $types);
},
'discount_value'
);
} //end if
$this->sortQuery($discounts);
$searchFields = trans('Type');
$discounts->orderBy('updated_at', 'DESC');
$discounts->with(['products.discount']);
$discounts = $discounts->paginate();
return view('back::pages.discounts.index', compact('discounts', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
/** @var \Illuminate\Database\Eloquent\Collection<mixed,\App\Models\Merchandise\Product> */
$existingProds = Product::all();
return view('back::pages.discounts.create', \compact(
'existingProds'
));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Discounts\StoreDiscountRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreDiscountRequest $request)
{
return DB::transaction(function () use ($request) {
$discount = new Discount();
$discount->fill($request->validated());
if ($discount->saveOrFail()) {
$discount->associateProducts($request->prods);
$discount->saveOrFail();
return redirect()
->route('bo.discounts.edit', $discount)
->with('bo.success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.discounts'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Shop\Discount $discount
* @return \Illuminate\Http\Response
*/
public function edit(Discount $discount)
{
/** @var \Illuminate\Database\Eloquent\Collection<mixed,\App\Models\Merchandise\Product> */
$selectedProds = $discount->products;
/** @var \Illuminate\Database\Eloquent\Collection<mixed,\App\Models\Merchandise\Product> */
$existingProds = Product::all();
return view('back::pages.discounts.edit', [
'discountModel' => $discount,
'existingProds' => $existingProds,
'selectedProds' => $selectedProds
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Discounts\UpdateDiscountRequest $request
* @param \App\Models\Shop\Discount $discount
* @return \Illuminate\Http\Response
*/
public function update(UpdateDiscountRequest $request, Discount $discount)
{
return DB::transaction(function () use ($request, $discount) {
// * Minicrud fill
$discount->fill($request->validated());
$discount->makeStatusSense();
$discount->associateProducts($request->prods);
if ($discount->saveOrFail()) {
return redirect()
->route('bo.discounts.edit', $discount)
->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.discounts'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Shop\Discount $discount
* @return \Illuminate\Http\Response
*/
public function destroy(Discount $discount)
{
if ($discount->deleteOrFail()) {
return redirect()
->route('bo.discounts.index')
->with('bo.warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.discounts'))]
));
}
return back();
}
}

View file

@ -1,70 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Faq;
use Illuminate\Http\Request;
class FaqController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$faq = Faq::all();
return view('bo.faq.index', compact('faq'));
}
/**
* Update faq data.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function saveDatas(Request $request)
{
$faq = $request->faq;
$looplimit = count($faq);
for ($i = 0; $i < $looplimit; $i++) {
$faqToInsert = !empty($faq[$i]["question"]) && !empty($faq[$i]["answer"]);
if (isset($faq[$i]["id"]) && $faq[$i]["id"] !== null && $faqToInsert) {
$f = Faq::findOrFail($faq[$i]["id"]);
$f->question = $faq[$i]["question"];
$f->answer = $faq[$i]["answer"];
$f->save();
} else {
if ($faqToInsert) {
$c = Faq::updateOrCreate([
'question' => $faq[$i]["question"],
'answer' => $faq[$i]["answer"]
]);
$faq[$i]["id"] = $c->id;
} else {
unset($faq[$i]);
$i--;
}
}
}
return $faq;
}
/**
* Remove the specified resource from storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function destroy(Request $request)
{
$faq = Faq::findOrFail($request->id);
$faq->delete();
return "Suppression terminée !";
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\UpdateGeneralConfigRequest;
use App\Models\Site\GeneralConfig;
class GeneralConfigController extends Controller
{
/**
* General config index
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index()
{
/** @var \Illuminate\Pagination\LengthAwarePaginator $configList */
$configList = GeneralConfig::query()->where('data', '!=', '[]')->paginate();
return view('back::pages.general-configs.index', compact('configList'));
}
/**
* Edit general config
*
* @param \App\Models\Site\GeneralConfig $config
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factor
*/
public function edit(GeneralConfig $config)
{
return view('back::pages.general-configs.edit', ['config' => $config]);
}
/**
* Update General
*
* @param \App\Http\Requests\UpdateGeneralConfigRequest $request
* @param \App\Models\Site\GeneralConfig $config
* @return voi\Illuminate\Http\RedirectResponse
*/
public function update(UpdateGeneralConfigRequest $request, GeneralConfig $config)
{
$config->fill(array_replace_recursive(
\json_decode(\json_encode($config->toArray()), true),
$request->validated()
));
$config->saveOrFail();
return redirect()->route('bo.general_configs.edit', $config)
->with('bo.success', 'Enregistré !');
}
}

View file

@ -1,204 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Magasin;
use App\Models\CollecteException;
use Illuminate\Http\Request;
class MagasinsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$magasins = Magasin::all();
return view('bo.magasins.index', compact('magasins'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.magasins.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'address' => 'required|string|max:255',
'postcode' => 'required',
'city' => 'required|string|max:255',
]);
$shop = Magasin::create($request->except('_token'));
return redirect()->route('bo.magasins.edit', $shop->id)->with('success', 'Nouveau Magasin enregistré !');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$magasin = Magasin::findOrFail($id);
$exceptionsCollecte = $magasin->collecteExceptions()->orderBy('date')->get();
return view('bo.magasins.edit', compact('magasin', 'exceptionsCollecte'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$request->validate([
'name' => 'required|string|max:255',
'address' => 'required|string|max:255',
'postcode' => 'required',
'city' => 'required|string|max:255',
]);
$shop = Magasin::findOrFail($id);
$shop->fill($request->except('_token'))->save();
return redirect()->route('bo.magasins.edit', $shop->id)->with('success', 'Modifications enregistrés !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$shop = Magasin::findOrFail($id);
$count = Magasin::count();
if ($count === 1) {
return redirect()->back()
->withErrors("Suppression impossible : Vous devez conserver au moins 1 point de retrait.");
}
$shop->delete();
return redirect()->route('bo.magasins.index');
}
/**
* bo
* ajx : affichage d'infos sur un magasin
*
* @param integer $id
* @param string|null $mode
* @return \Illuminate\Contracts\View\View
*/
public function getMagasinInfo(int $id, string $mode = null)
{
$magasin = Magasin::findOrFail($id);
if (!is_null($mode)) {
if ($mode == "tippy") {
return view('bo.magasins.tippy-card', compact('magasin'));
}
if ($mode == "json") {
return $magasin;
}
return view('bo.magasins.info-card', compact('magasin'));
}
return view('bo.magasins.info-popup', compact('magasin'));
}
/**
* Save collect exceptions
*
* @param Request $request
* @return array
*/
public function saveExceptions(Request $request)
{
$collecteException = $request->collecte_exception;
$looplimit = count($collecteException);
for ($i = 0; $i < $looplimit; $i++) {
$collecteExceptionToInsert = !empty($collecteException[$i]["date"]);
if (
isset($collecteException[$i]["id"]) &&
$collecteException[$i]["id"] !== null &&
$collecteExceptionToInsert
) {
$ce = CollecteException::findOrFail($collecteException[$i]["id"]);
$ce->date = $collecteException[$i]["date"];
$ce->comment = $collecteException[$i]["comment"];
$ce->magasin_id = $collecteException[$i]["magasin_id"];
$ce->save();
} else {
if ($collecteExceptionToInsert) {
$ce = CollecteException::updateOrCreate([
'date' => $collecteException[$i]["date"],
'comment' => $collecteException[$i]["comment"],
'magasin_id' => $collecteException[$i]["magasin_id"]
]);
$collecteException[$i]["id"] = $ce->id;
} else {
unset($collecteException[$i]);
$i--;
}
}
}//end for
// Remet dans l'ordre avant de renvoyer.
$this->arraySortByColumn($collecteException, "date");
return $collecteException;
}
/**
* helper function
*
* @param array $array
* @param string $column
* @param integer $direction
* @return void
*/
public function arraySortByColumn(array &$array, string $column, int $direction = SORT_ASC)
{
$reference_array = array();
foreach ($array as $key => $row) {
$reference_array[$key] = $row[$column];
}
array_multisort($reference_array, $direction, $array);
}
/**
* delete collecte exception
*
* @param Request $request
* @return string
*/
public function destroyExceptions(Request $request)
{
$collecteException = CollecteException::findOrFail($request->id);
$collecteException->delete();
return "Suppression terminée !";
}
}

View file

@ -0,0 +1,610 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\Order\DeliveryModeEnum;
use App\Enums\Order\DeliveryStatus;
use App\Enums\Order\PaymentModeEnum;
use App\Enums\Order\TransactionStatus;
use App\Enums\Order\TransactionType;
use App\Enums\Order\ValidationStatus;
use App\Events\OrderIsRefunded;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Orders\ChangeOrderDeliveryStatusRequest;
use App\Http\Requests\Bo\Orders\ChangeOrderValidationStatusRequest;
use App\Http\Requests\Bo\PrepareOrderRequest;
use App\Http\Requests\Bo\ResolveOrderRequest;
use App\Jobs\Order\RefundOrder;
use App\Lib\Helpers\ToolboxHelper;
use App\Models\Payment\Transaction;
use App\Models\Shop\Order;
use Gloudemans\Shoppingcart\CartItem;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\View\View;
class OrderController extends Controller
{
/**
* If true the index action will only display today orders
* @var false
*/
private $indexModeTodayOnly = false;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request): View
{
/** @var \Illuminate\Database\Eloquent\Builder $orders */
$orders = Order::query();
if ($request->search) {
$this->searchQuery(
$orders,
$request->search,
function (Builder $q) use ($request) {
$q->orWhereHas('transactions', function (Builder $q) use ($request) {
$foreignIds = \collect(PaymentModeEnum::toArray())
->map(function (PaymentModeEnum $paymentMode) use ($request) {
return Transaction::getPaymentForeignKeyName(
$paymentMode,
$request->search
);
});
$q->whereIn('foreign_id', $foreignIds)
->orWhereIn('foreign_refund_id', $foreignIds);
});
},
'reference',
'email',
);
}//end if
$this->sortQuery($orders);
$searchFields = trans('Réference,Email,Pay.Ref');
if ($this->indexModeTodayOnly) {
$orders->whereHas('timeslot', function (Builder $q) {
$q->whereDate('slot', \now()->today());
});
}
$orders->orderBy('created_at', 'DESC');
$orders->with(['deliveryMode', 'trashedTimeSlot.shop']);
$orders = $orders->paginate(10);
return view('back::pages.orders.index', compact('orders', 'searchFields'));
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function indexToday(Request $request): View
{
$this->indexModeTodayOnly = true;
/** @var \Illuminate\View\View $response */
$response = $this->callAction('index', ['request' => $request]);
return $response;
}
/**
* Display the specified resource.
*
* @param \App\Models\Shop\Order $order
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function show(Order $order): View
{
$order->load('transactions');
return view('back::pages.orders.show', compact('order'));
}
/**
* Mettre a jour le status de validation d'une commande
*
* @param \App\Http\Requests\Bo\Orders\ChangeOrderValidationStatusRequest $request
* @param \App\Models\Shop\Order $order
* @return \Illuminate\Http\RedirectResponse
*/
public function changeValidationStatus(ChangeOrderValidationStatusRequest $request, Order $order): RedirectResponse
{
$this->authorize('changeValidationStatus', $order);
return DB::transaction(function () use ($request, $order) {
$order->validation_status = $request->validation_status;
switch ($request->validation_status) {
case ValidationStatus::canceled()->value:
$order->delivery_status = DeliveryStatus::canceled();
\session()->flash('bo.warning', trans('La commande est marquée comme annulée'));
// Fall through.
case ValidationStatus::validated()->value:
$order->saveOrFail();
return \response()->redirectToRoute('bo.orders.show', $order)->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.orders'))]
));
case ValidationStatus::pending()->value:
return \back()->with('bo.errors', \trans('Il faut choisir un statut pour cette commande.'));
default:
throw new \RuntimeException("Unhandled status `{$request->validation_status}`");
}//end switch
});
}
/**
* Mettre a jour le status de livraison d'une commande
*
* @param \App\Http\Requests\Bo\Orders\ChangeOrderDeliveryStatusRequest $request
* @param \App\Models\Shop\Order $order
* @return \Illuminate\Http\RedirectResponse
*/
public function changeDeliveryStatus(ChangeOrderDeliveryStatusRequest $request, Order $order): RedirectResponse
{
$this->authorize('changeDeliveryStatus', $order);
return DB::transaction(function () use ($request, $order) {
$order->delivery_status = $request->delivery_status;
switch (true) {
// * La commande va être préparé ou est déja en cours de préparation
case $order->delivery_status->equals(DeliveryStatus::preparation()) or
$order->delivery_status->equals(DeliveryStatus::preparation()):
$order->delivery_status = DeliveryStatus::preparation();
$order->shipped_basket = '';
$order->saveOrFail();
return \redirect()->route('bo.orders.prepare', $order);
// * La commande est récupérée.
case $order->delivery_status->equals(DeliveryStatus::recovered()):
// * Déclarer la commande comme réglée si récupération à la boutique.
if ($order->deliveryMode->mode->equals(DeliveryModeEnum::inShop())) {
// ! There should be one and only one inShop transaction !
/** @var \App\Models\Payment\Transaction|null */
$inShopTransaction = $order->transactions()
->where('type', TransactionType::debit())
->where('payment_mode', PaymentModeEnum::inShop())
->latest()->first();
$paidTransactions = $order->transactions()->where('type', TransactionType::debit())
->where('status', TransactionStatus::validated())->count();
// * Check if there are not paid transactions then
// * we should have one pending inShop transaction.
if (!$paidTransactions and !$inShopTransaction) {
throw new \RuntimeException(
"There should be one inshop transaction for order '{$order->id}'"
);
}
if ($inShopTransaction) {
$inShopTransaction->status = TransactionStatus::validated();
$inShopTransaction->saveOrFail();
}
} else {
throw new \RuntimeException("Unhandled delivery mode other than in shop");
} //end if
// Fall through.
// * La commande est prête.
case $order->delivery_status->equals(DeliveryStatus::ready()):
$order->saveOrFail();
return \back()->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.orders'))]
));
default:
throw new \RuntimeException("Unhandled status `{$request->delivery_status}`");
}//end switch
});
}
/**
* Show the order resolve form
*
* @param \App\Models\Shop\Order $order
* @return \Illuminate\View\View
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function resolve(Order $order): View
{
// phpcs:enable
$this->authorize('resolve', $order);
$transactionModel = $order->transactions()->where('needs_validation', true)->first();
// * Diff between Basket - (Delivered items - refunded transaction items)
$remainingItems = $order->getRemainingUnrefundedItems();
return \view('back::pages.orders.resolve', \compact('order', 'transactionModel', 'remainingItems'));
}
/**
* Validate order resolution
*
* @param \App\Http\Requests\Bo\ResolveOrderRequest $request
* @param \App\Models\Shop\Order $order
* @return \Illuminate\Http\RedirectResponse
*/
public function resolveSubmit(ResolveOrderRequest $request, Order $order): RedirectResponse
{
return DB::transaction(function () use ($request, $order) {
/** @var \App\Models\Payment\Transaction */
$transaction = $request->transaction;
/** @var \Illuminate\Support\Collection<object> */
$orderedItems = \collect($order->basket);
// * getRemainingUnrefundedItems Modifie les rowId originaux, il faut les restaurer pour la comparaison.
/** @var \Illuminate\Support\Collection<string,string> */
$refundedItems = $request->collect('items')->mapWithKeys(function (array $item) use ($orderedItems) {
$item = (object)$item;
$rowId = $orderedItems->where('id', '=', $item->index)->first()->rowId;
return [$rowId => $item->quantity];
});
/** @var \Illuminate\Support\Collection<object> */
$refundedTransactionsItems = $orderedItems->map(function (object $basketItem) use ($refundedItems) {
if ($refundedItems->has($basketItem->rowId)) {
$basketItem->qty = \intval($refundedItems->get($basketItem->rowId));
return $basketItem;
}
return null;
});
$refundedTransactionsItems = \collect(ToolboxHelper::updateBasketAmounts(
$orderedItems,
$refundedItems->all()
));
// ? La transaction concerne des produits ?
if ($refundedTransactionsItems->count()) {
// * Estampiller les ligne avec la raison
$refundedTransactionsItems->map(function (object $basketItem) use ($request) {
$basketItem->name = "{$request->reason} - {$basketItem->name}";
return $basketItem;
});
}
$montantRembourse = \intval($refundedTransactionsItems->sum('subtotal'));
// ? Le montant des produits remboursé est inférieur a celui de la transaction.
if ($montantRembourse < \intval($transaction->amount->getAmount())) {
// * Créer un dummy product pour représenter la transaction.
$cartItemOptions = \array_merge(static::$cartItemOptionsSkel, [
'virtual' => true,
'price_ttc' => \intval($transaction->amount->getAmount()) - $montantRembourse,
]);
$cartItem = CartItem::fromArray([
'id' => \str_replace('.', '', \uniqid('', true)),
'name' => $request->reason,
'price' => \intval($transaction->amount->getAmount()) - $montantRembourse,
'weight' => 0,
'options' => $cartItemOptions
]);
$cartItem->setQuantity(1);
// * On envoie la ligne virtuelle en haut de la liste.
$refundedTransactionsItems->put($cartItem->rowId, \json_decode($cartItem->toJson()));
$refundedTransactionsItems = $refundedTransactionsItems->reverse();
} //end if
$transaction->items = $refundedTransactionsItems->map(function (object $basketItem) {
$basketItem->tax = $basketItem->tax * -1;
$basketItem->price = $basketItem->price * -1;
$basketItem->subtotal = $basketItem->subtotal * -1;
$basketItem->options->price_ttc = $basketItem->options->price_ttc * -1;
$basketItem->options->undiscounted_price_ttc =
$basketItem->options->undiscounted_price_ttc * -1;
return $basketItem;
});
$transaction->needs_validation = false;
// ? The resolution means a complete refund ?
if ($order->isCompleteRefundWithTransaction($transaction)) {
// * Calculate unrefunded tax
$unRefundedTax = \abs(\intval(\collect($order->getRemainingUnrefundedItems())->sum(function (object $item) {
return $item->tax * $item->qty;
})));
// * Calculate justified items tax
$justifiedItemTax = \abs(\intval(\collect($transaction->items)->sum(function (object $item) {
return \optional($item->options)->virtual ? 0 : $item->tax * $item->qty;
})));
// * Update remaining Tax to exclude justified.
$remainingTax = ($unRefundedTax - $justifiedItemTax) * -1;
// * Set remaining tax to virtual
$transaction->items = (object)\collect($transaction->items)
->map(function (object $item) use ($remainingTax) {
if (\optional($item->options)->virtual) {
$item->tax = $remainingTax;
}
return $item;
})->all();
} //end if
$transaction->saveOrFail();
OrderIsRefunded::dispatch(
$order,
$transaction
);
return \response()->redirectToRoute('bo.orders.show', $order)
->with('success', \trans('La transaction est marquée comme résolue'));
});
}
/**
* Show the order prepare form
*
* @param \App\Models\Shop\Order $order
* @return \Illuminate\View\View
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function prepare(Order $order): View
{
// phpcs:enable
$this->authorize('prepare', $order);
$remainingItems = $order->getRemainingUnrefundedItems();
return \view('back::pages.orders.prepare', \compact('order', 'remainingItems'));
}
/**
* Validate order preparation
*
* @param \App\Http\Requests\Bo\PrepareOrderRequest $request
* @param \App\Models\Shop\Order $order
* @return \Illuminate\Http\RedirectResponse
*/
public function prepareSubmit(PrepareOrderRequest $request, Order $order): RedirectResponse
{
$this->authorize('prepare', $order);
return DB::transaction(function () use ($request, $order) {
// * Les items qu'il reste sur la commande (non remboursés).
$remainingItems = $order->getRemainingUnrefundedItems();
// * On met à jour cette liste avec les items effectivement préparés dans la commande.
$shipped_basket = \collect(ToolboxHelper::updateBasketAmounts(
$remainingItems,
$request->items
));
// * On met à jour la liste avec les items qui vont être remboursés.
$itemsToRefund = $remainingItems->pluck('qty', 'id')
->mapWithKeys(function ($qty, string $id) use ($request) {
return [
$id => \intval($qty) - \intval(\collect($request->items)->get($id, 0))
];
});
$refundedTransactionsItems = \collect(ToolboxHelper::updateBasketAmounts(
$remainingItems,
$itemsToRefund->all()
));
$order->shipped_basket = $shipped_basket;
$order->delivery_status = DeliveryStatus::ready();
// * Save Shipped basket
$order->saveQuietly();
// ? Le montant va exceder ce que le client a payé ?
if ($request->willExceedOrderAmount) {
// * Annulation et remboursement du reste.
// * Créer un dummy product pour représenter la transaction.
$cartItemOptions = \array_merge(static::$cartItemOptionsSkel, [
'virtual' => true,
'price_ttc' => $order->total_ttc - $order->total_refund,
]);
$cartItem = CartItem::fromArray([
'id' => \str_replace('.', '', \uniqid('', true)),
'name' => trans('Remboursement commande'),
'price' => $order->total_ttc - $order->total_refund,
'weight' => 0,
'options' => $cartItemOptions
]);
$cartItem->setQuantity(1);
$this->refundTransaction(
$order,
$order->total_ttc - $order->total_refund,
// * Negate all items prices (already refunded)
\collect([
$cartItem->rowId => \json_decode($cartItem->toJson())
])->map(function (object $basketItem) {
$basketItem->tax = $basketItem->tax * -1;
$basketItem->price = $basketItem->price * -1;
$basketItem->subtotal = $basketItem->subtotal * -1;
$basketItem->options->price_ttc = $basketItem->options->price_ttc * -1;
$basketItem->options->undiscounted_price_ttc =
$basketItem->options->undiscounted_price_ttc * -1;
return $basketItem;
})
);
// * Mettre a jour la commande.
$order->saveOrFail();
// * Cancel Order completely.
/** @var \Illuminate\Http\RedirectResponse */
return $this->callAction('changeValidationStatus', [
ChangeOrderValidationStatusRequest::create(
\route('bo.orders.validation-status', $order),
'POST',
[
'validation_status' => ValidationStatus::canceled()->value
]
),
$order
]);
} //end if
// ? La commande va devoir être remboursé completement ?
if ($request->willNeedCompleteRefund) {
$this->refundTransaction(
$order,
$order->total_ttc - $order->total_refund,
// * Negate all items prices (already refunded)
$order->getRemainingUnrefundedItems()->map(function (object $basketItem) {
$basketItem->tax = $basketItem->tax * -1;
$basketItem->price = $basketItem->price * -1;
$basketItem->subtotal = $basketItem->subtotal * -1;
$basketItem->options->price_ttc = $basketItem->options->price_ttc * -1;
$basketItem->options->undiscounted_price_ttc =
$basketItem->options->undiscounted_price_ttc * -1;
return $basketItem;
})
);
// * Mettre a jour la commande.
$order->saveOrFail();
// * Cancel Order completely.
/** @var \Illuminate\Http\RedirectResponse */
return $this->callAction('changeValidationStatus', [
ChangeOrderValidationStatusRequest::create(
\route('bo.orders.validation-status', $order),
'POST',
[
'validation_status' => ValidationStatus::canceled()->value
]
),
$order
]);
} //end if
// ? Il faudra emettre un remboursement pour le reste ?
if ($request->willNeedRefund and (bool)intval($refundedTransactionsItems->sum('subtotal'))) {
$this->refundTransaction(
$order,
$refundedTransactionsItems->sum('subtotal'),
// * Negate all items prices (already refunded)
$refundedTransactionsItems->map(function (object $basketItem) {
$basketItem->tax = $basketItem->tax * -1;
$basketItem->price = $basketItem->price * -1;
$basketItem->subtotal = $basketItem->subtotal * -1;
$basketItem->options->price_ttc = $basketItem->options->price_ttc * -1;
$basketItem->options->undiscounted_price_ttc =
$basketItem->options->undiscounted_price_ttc * -1;
return $basketItem;
})
);
}
// * Mettre a jour la commande.
$order->saveOrFail();
return \redirect()->route('bo.orders.show', $order)->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.orders'))]
));
});
}
/**
* Complete order refund and cancel
*
* @param \App\Models\Shop\Order $order
* @return \Illuminate\Http\RedirectResponse
*/
public function cancelRefundSubmit(Order $order): RedirectResponse
{
$this->authorize('refund', $order);
return DB::transaction(function () use ($order) {
$this->callAction('refundSubmit', [$order]);
// * Cancel Order completely.
/** @var \Illuminate\Http\RedirectResponse */
return $this->callAction('changeValidationStatus', [
ChangeOrderValidationStatusRequest::create(
\route('bo.orders.validation-status', $order),
'POST',
[
'validation_status' => ValidationStatus::canceled()->value
]
),
$order
]);
});
}
/**
* Complete order refund
*
* @param \App\Models\Shop\Order $order
* @return \Illuminate\Http\RedirectResponse
*/
public function refundSubmit(Order $order): RedirectResponse
{
$this->authorize('refund', $order);
return DB::transaction(function () use ($order) {
$this->refundTransaction(
$order,
$order->total_ttc - $order->total_refund,
$order->getItemForOrderCancelling()
);
return \back()->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.orders'))]
));
});
}
/**
* Refund a transaction
*
* @param \App\Models\Shop\Order $order
* @param integer $amount
* @param object $items
* @return void
* @throws \RuntimeException If the payment transaction cannot be fetched for the concerned order.
*/
private function refundTransaction(
Order $order,
int $amount,
object $items
): void {
if (!($transaction = $order->getTransactionUsedForPayment())) {
throw new \RuntimeException("Could not fetch paiment transaction for order {$order->id}");
}
// ? Le paiment se fait en boutique ?
if ($transaction->payment_mode->equals(PaymentModeEnum::inShop())) {
// * Execute juste l'adaptateur.
$newTransaction = $transaction->getAdapter()->doRefundCustomer(
$transaction,
$amount,
$items
);
// ? On est sur la même transaction ?
if ($newTransaction->exists and $newTransaction->id === $transaction->id) {
// * Elle a été anullée.
$previousMessage = session()->get('bo.warning', '');
session()->flash('bo.warning', \trans(
'La commande est annulée.'
) . " {$previousMessage}");
return;
}
// * Une nouvelle à été créé.
$previousMessage = session()->get('bo.error', '');
session()->flash(
'bo.info',
\trans(
'Une nouvelle transaction est crée avec les produits effectivement préparés.'
) . " {$previousMessage}"
);
return;
} //end if
RefundOrder::dispatchSync(
$order,
$transaction,
$amount,
$items
);
$previousMessage = session()->get('bo.info', '');
session()->flash('bo.info', \trans(
'Un remboursement va être émit au client'
) . " \n{$previousMessage}");
}
}

View file

@ -1,610 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Order;
use App\Models\OrderLine;
use App\Models\OrdersProducts;
use App\Models\Product;
use App\Models\Timeslot;
use App\Models\Magasin;
use App\Http\Requests\SortOrders;
use Illuminate\Support\Facades\Mail;
use App\Mail\OrderStatusMail;
use App\Mail\OrderConfirmMail;
use App\Mail\OrderConfirmAdminMail;
use App\Jobs\CsvOrderWrite;
use App\Models\Customers;
use App\Models\Setting;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterval;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class OrdersController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \App\Http\Requests\SortOrders $request
* @return \Illuminate\Contracts\View\View
*/
public function index(SortOrders $request)
{
$orders = $this->filterOrders($request);
$orders = $orders->paginate(10);
return view('bo.orders.index', compact('orders'));
}
/**
* orders that ship today
*
* @param SortOrders $request
* @return \Illuminate\Contracts\View\View
*/
public function today(SortOrders $request)
{
$orders = $this->filterOrders($request);
// Filter today.
$today = CarbonImmutable::now()->startOfDay();
$orders = $orders->whereHas('timeslot', function ($q) use ($today) {
$q->where('day', '=', $today->format('d/m/Y'));
});
$orders = $orders->get();
return view('bo.orders.index', compact('orders', 'today'));
}
/**
* Filter orders
*
* @param Request $request
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function filterOrders(Request $request)
{
$orders = Order::query();
if ($request->rst) {
Session::remove('orders.sort_col');
Session::remove('orders.sort_way');
}
if ($request->sort_col) {
Session::put('orders.sort_col', $request->sort_col);
Session::save();
}
if ($request->sort_way) {
Session::put('orders.sort_way', $request->sort_way);
Session::save();
}
if (Session::has('orders.sort_col') and Session::has('orders.sort_way')) {
switch (Session::get('orders.sort_col')) {
case 'date':
$orders = $orders->orderBy('date', Session::get('orders.sort_way'));
break;
case 'status_id':
$orders = $orders->orderBy('status_id', Session::get('orders.sort_way'));
break;
case 'magasin_id':
$orders = $orders->select('orders.*');
$orders = $orders->leftJoin('timeslots', 'timeslots.order_id', '=', 'orders.id');
$orders = $orders->leftJoin('magasins', 'timeslots.magasin_id', '=', 'magasins.id');
$orders = $orders->orderBy('magasins.id', Session::get('orders.sort_way'));
break;
case 'date_retrait':
$orders = $orders->select('orders.*');
$orders = $orders->leftJoin('timeslots', 'timeslots.order_id', '=', 'orders.id');
$orders = $orders->orderBy('timeslots.day', Session::get('orders.sort_way'));
$orders = $orders->orderBy('timeslots.time', Session::get('orders.sort_way'));
break;
case 'customer_id':
$orders = $orders->orderBy('customer_id', Session::get('orders.sort_way'));
break;
}//end switch
}//end if
return $orders;
}
/**
* action de changement de statut vers l'avant
* envoi de mails transctionnels
*
* @param string $reference Reference de la commande.
* @return \Illuminate\Http\RedirectResponse
*/
public function changeStatus(string $reference)
{
$order = Order::where('reference', $reference)->first();
$order->status_id = $order->status_id + 1;
$order->save();
// Send confirm mail.
Mail::to($order->customer->email)->send(new OrderStatusMail($order));
return back()->with('success', "Changement de status enregistré pour la commande " . $order->reference . ".");
}
/**
* mise au statut annulé d'une commande
*
* @param string $reference
* @return \Illuminate\Http\RedirectResponse
*/
public function cancelOrder(string $reference)
{
$order = Order::where('reference', $reference)->first();
$order->status_id = 7;
$order->save();
return back()->with('success', "La commande " . $order->reference . " à été annulée.");
}
/**
* @deprecated Supprimer cette méthode qui repose sur un modèle de donnée osolète.
* ajout de produits à la commande
*
* @param string $orderReference
* @param integer $productId
* @param integer $quantity
* @return \Illuminate\Http\RedirectResponse
*/
public function addProduct(string $orderReference, int $productId, int $quantity)
{
$product = Product::findOrFail($productId);
$order = Order::where('reference', $orderReference)->first();
$order->total = $order->total + (($product->price - $product->price_buy) * $quantity);
$orderProduct = new OrdersProducts();
$orderProduct->product_id = $product->id;
$orderProduct->order_id = $order->id;
$orderProduct->quantity = $quantity;
$orderProduct->save();
return redirect()->route('bo.orders')->with('success', "Le produit à été ajouté.");
}
/**
* front
* récap panier du tunnel de commande
*
* @return \Illuminate\Contracts\View\View
*/
public function recapOrder()
{
$activestep = 2;
Cart::instance('panier');
$cart = Cart::content();
$cartInfos = array(
"totalTTC" => Cart::total(),
// Total des taxes.
'totalTVA' => Cart::tax(),
// Total sans les taxes.
'totalHT' => Cart::subtotal(),
// Poids total des items.
'totalweight' => Cart::weight(),
// Total avant taxes et remises.
'totalHTbefore' => Cart::priceTotal(),
// Total des remises.
'totaldiscount' => Cart::discount(),
// Nb de produits dans le cart.
'countItems' => Cart::count(),
);
return view('sales.recap', compact('activestep', 'cart', 'cartInfos'));
}
/**
* front
* Storing order from sales tunnel
*
* @return \Illuminate\Http\RedirectResponse
*/
public function store()
{
// Save.
Cart::instance('panier');
$order = new Order();
// Generate order ref.
$order->reference = $this->generateOrderRef();
$order->date = Carbon::now();
$order->status_id = 1;
$order->total = str_replace(',', '.', str_replace(' ', '', Cart::total()));
$order->customer_id = Auth::user()->id;
$order->save();
// Order_id.
Cart::instance('panier');
$cart = Cart::content();
foreach ($cart as $cartItem) {
$order_line = new OrderLine();
$order_line->order_id = $order->id;
$order_line->product_id = $cartItem->model->id;
$order_line->quantity = $cartItem->qty;
// Gross unit price.
$order_line->unit_price = $cartItem->model->priceHt;
// Discount per unit.
$order_line->unit_discount = $cartItem->model->discountAmount;
// Tax rate for this line.
$order_line->tax_rate = $cartItem->taxRate;
// Tax amount par unit.
$order_line->unit_tax = round(
(($order_line->unit_price - $order_line->unit_discount) * ($order_line->tax_rate / 100)) * 100
) / 100;
// Discount amount for the line.
$order_line->total_discount = $order_line->unit_discount * $order_line->quantity;
// Gross amount for the line.
$order_line->total_ht = ($order_line->unit_price - $order_line->unit_discount) * $order_line->quantity;
// Tax amount for the line.
$order_line->total_tax = $order_line->unit_tax * $order_line->quantity;
// Total TTC avec remises de la ligne.
$order_line->total_ttc = $order_line->total_ht + $order_line->total_tax;
$order_line->save();
}//end foreach
// Empty cart.
Cart::destroy();
// Then redirect.
return redirect()->route('sales.pick-slot', ['reference' => $order->reference]);
}
/**
* generate order Reference
* for a new order
*
* @return string
*/
public function generateOrderRef()
{
// Format : YYYYMMDDHHMM000.
$now = Carbon::now();
// Count todays orders.
$countorders = Order::whereDate('created_at', Carbon::today())->count();
// Add one.
$countorders++;
$ref = $now->isoFormat('YYYYMMDDHHmm') . str_pad($countorders, 3, "0", STR_PAD_LEFT);
return $ref;
}
/**
* front tunnel de commande étape 3 : choix de créneau
*
* @param string $reference
* @return \Illuminate\Contracts\View\View
*/
public function timeSlotPick(string $reference)
{
$activestep = 3;
if (config('theme.multi_relay')) {
$magasin = Magasin::all();
} else {
$magasin = Magasin::first();
}
$availCreneaux = array();
$order = Order::where('reference', '=', $reference)->firstOrFail();
return view('sales.pick-slot', compact('activestep', 'availCreneaux', 'order', 'magasin'));
}
/**
* add timeslot by interval between start and end hour
*
* @param array $plage
* @param integer $weekDay
* @param CarbonImmutable $current
* @param Magasin $magasin
* @param CarbonImmutable $now
* @param CarbonImmutable $today
* @param CarbonInterval $delaiPrepa
* @param CarbonInterval $interval
* @param Carbon $currentTime
* @param Carbon $endTime
* @return array
*/
private function addCreneaux(
array $plage,
int $weekDay,
CarbonImmutable $current,
Magasin $magasin,
CarbonImmutable $now,
CarbonImmutable $today,
CarbonInterval $delaiPrepa,
CarbonInterval $interval,
Carbon $currentTime,
Carbon $endTime
) {
$retArray = array();
do {
$creneau = $currentTime->format('H:i');
$nbtakens = $magasin->timeslots()
->where('day', $current->format('d/m/Y'))
->where('time', $creneau)->count();
if ($nbtakens >= $plage[$weekDay]['max_orders_per_interval']) {
$isDispo = false;
} else {
$isDispo = true;
}
if (
$current->addHours($currentTime->format('H'))->addMinutes($currentTime->format('i')) <
$now->add($delaiPrepa)
) {
$isDispo = false;
}
// Attention si la date est passée (today mais avant maintenant) : pas de créneau.
if ($current > $today || $currentTime >= $now) {
$retArray[] = array($creneau, $isDispo);
}
$currentTime->add($interval);
} while ($currentTime <= $endTime);
return $retArray;
}
/**
* give array of available crenaux
*
* @param integer $id
* @return array
*/
public function getAvailableCreneauxByShop(int $id)
{
// Setting à stocker et réutiliser.
$plage_days = Setting::where('key', 'order_withdrawal_period')->firstOrFail()->value;
$magasin = Magasin::findOrFail($id);
$timeslotSettings = $magasin->timeslotSettings;
// $timeslotSettings = TimeslotSetting::all();
// Fabriquons un tableau recap.
$plage = array();
foreach ($timeslotSettings as $day) {
$plage[$day->day_index] = array(
'morning_start' => $day->morning_start,
'morning_end' => $day->morning_end,
'afternoon_start' => $day->afternoon_start,
'afternoon_end' => $day->afternoon_end,
'interval' => $day->interval,
'min_order_preparation' => $day->min_order_preparation,
'max_orders_per_interval' => $day->max_orders_per_interval
);
}
// Génération des plages.
// Start today.
$today = CarbonImmutable::now()->startOfDay();
$weekDay = $today->weekday();
// Final boundary (permet de limiter la liste des jours d'exception).
$finalday = $today->add($plage_days, 'day');
$now = CarbonImmutable::now();
// Jours exceptions.
$noCollectDays = $magasin->collecteExceptions()
->whereDate('date', '>', $now)
->whereDate('date', '<=', $finalday)
->orderBy('date')->get();
$delaiPrepa = CarbonInterval::createFromFormat('h:i', $plage[$weekDay]['min_order_preparation'] ?? '01:00');
$availCreneaux = array();
for ($i = 0; $i < $plage_days; $i++) {
$current = $today->add($i, 'day');
$weekDay = $current->weekday();
$interval = CarbonInterval::createFromFormat('h:i', $plage[$weekDay]['interval'] ?? '00:15');
// Si c'est un jour exception skip.
$skip = false;
foreach ($noCollectDays as $not_today) {
if ($current->isSameDay($not_today->date)) {
$skip = true;
}
}
if ($skip) {
continue;
}
$availCreneaux[$current->format('d/m/Y')] = array();
// Matinée.
if (!empty($plage[$weekDay]['morning_start'])) {
$currentTime = Carbon::createFromTimeString($plage[$weekDay]['morning_start']);
$endTime = Carbon::createFromTimeString($plage[$weekDay]['morning_end']);
$morningCreneaux = $this->addCreneaux(
$plage,
$weekDay,
$current,
$magasin,
$now,
$today,
$delaiPrepa,
$interval,
$currentTime,
$endTime
);
$availCreneaux[$current->format('d/m/Y')] = array_merge(
$availCreneaux[$current->format('d/m/Y')],
$morningCreneaux
);
} //end if
// Aprem.
if (!empty($plage[$weekDay]['afternoon_start'])) {
$currentTime = Carbon::createFromTimeString($plage[$weekDay]['afternoon_start']);
$endTime = Carbon::createFromTimeString($plage[$weekDay]['afternoon_end']);
$afternoonCreneaux = $this->addCreneaux(
$plage,
$weekDay,
$current,
$magasin,
$now,
$today,
$delaiPrepa,
$interval,
$currentTime,
$endTime
);
$availCreneaux[$current->format('d/m/Y')] = array_merge(
$availCreneaux[$current->format('d/m/Y')],
$afternoonCreneaux
);
} //end if
}//end for
return $availCreneaux;
}
/**
* Call ajax pour le composant vue de timeslots.
*
* @param Request $request
* @return array
*/
public function getAvailableCreneaux(Request $request)
{
return $this->getAvailableCreneauxByShop($request->id);
}
/**
* front
* enregistrement du créneau de collect
* et validation de la commande
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function setTimeSlot(Request $request)
{
$request->validate([
'selectedmagasin' => 'required',
'selectedcreneau' => 'required',
'ok_cgv' => 'accepted',
]);
$time_slot = Carbon::createFromFormat('d/m/Y-H:i', $request->selectedcreneau);
$tsday = explode('-', $request->selectedcreneau)[0];
$tstime = explode('-', $request->selectedcreneau)[1];
$magasin_id = $request->selectedmagasin;
$reference = $request->order_ref;
$order = Order::where('reference', '=', $reference)->firstOrFail();
if ($order->status_id == 1) {
// On ne traite l'enregistrement que pour les commandes en attente venant de l'étape de récap.
// Save timeslot.
$timeslot = Timeslot::firstOrNew(['order_id' => $order->id]);
$timeslot->order_id = $order->id;
$timeslot->magasin_id = $magasin_id;
$timeslot->day = $tsday;
$timeslot->time = $tstime;
$timeslot->save();
// Save order status.
$order->status_id = 3;
$order->save();
// Send confirm mail.
Mail::to($order->customer->email)->queue(new OrderConfirmMail($order));
// Envoi du mail de confirmation aux admins site.
$toMailSetting = Setting::where('key', 'send_order_confirm_mail_to')->first();
if (empty($toMailSetting->value)) {
$toMail = config('mail.from.address');
} else {
$toMail = $toMailSetting->value;
}
Mail::to($toMail)->queue(new OrderConfirmAdminMail($order));
// Export commande.
if (config('bosettings.regate_orders')) {
CsvOrderWrite::dispatch($order);
}
}//end if
// Redirect to final confirmation.
return redirect()->route('sales.confirm', ['reference' => $order->reference]);
}
/**
* generate Order
* Test method
*
* @param string $reference
* @return void
*/
public function generateOrder(string $reference)
{
$order = Order::where('reference', '=', $reference)->with('lines')->firstOrFail();
$job = new CsvOrderWrite($order);
$job->handle();
dd('end');
}
/**
* front
* page de confirmation de commande
*
* @param string $reference
* @return \Illuminate\Contracts\View\View
*/
public function confirmOrder(string $reference)
{
$activestep = 4;
$idcommande = Session::get('idcommande');
$order = Order::where('reference', '=', $reference)->firstOrFail();
// 1 seul magasin en base pour le moment
$magasin = Magasin::find($order->timeslot->magasin->id);
return view('sales.confirm', compact('activestep', 'order', 'magasin'));
}
/**
* bo
* ajx : affichage d'infos sur un client
*
* @param integer $id
* @param string|null $mode
* @return \Illuminate\Contracts\View\View
*/
public function getCustomerInfo(int $id, string $mode = null)
{
$cust = Customers::findOrFail($id);
if (!is_null($mode)) {
if ($mode == "tippy") {
return view('bo.customer.tippy-card', compact('cust'));
}
if ($mode == "json") {
return $cust->apiData;
}
return view('bo.customer.info-card', compact('cust'));
}
return view('bo.customer.info-popup', compact('cust'));
}
/**
* bo : page détail commande
*
* @param string $reference
* @return \Illuminate\Contracts\View\View
*/
public function orderDetail(string $reference)
{
$order = Order::where('reference', '=', $reference)->firstOrFail();
return view('bo.orders.detail', compact('order'));
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\PaymentMode\UpdatePaymentModeRequest;
use App\Models\Shop\PaymentMode;
use Illuminate\Support\Str;
class PaymentModeController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$paymentModes = PaymentMode::paginate();
return view('back::pages.payment-modes.index', compact('paymentModes'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Shop\PaymentMode $paymentMode
* @return \Illuminate\Http\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function edit(PaymentMode $paymentMode)
{
$this->authorize('update', PaymentMode::class);
return view('back::pages.payment-modes.edit', compact(
'paymentMode'
));
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\UpdatePaymentModeRequest $request
* @param \App\Models\Shop\PaymentMode $paymentMode
* @return \Illuminate\Http\Response
*/
public function update(UpdatePaymentModeRequest $request, PaymentMode $paymentMode)
{
$this->authorize('update', PaymentMode::class);
$paymentMode->fill(array_replace_recursive(
\json_decode(\json_encode($paymentMode->toArray()), true),
$request->validated()
));
if ($paymentMode->saveOrFail()) {
return redirect()->back()
->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.payment_modes'))]
));
}
return back();
}
}

View file

@ -0,0 +1,384 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Products\CreateProductRequest;
use App\Http\Requests\Bo\Products\StoreProductRequest;
use App\Http\Requests\Bo\Products\UpdateProductRequest;
use App\Models\Merchandise\Product;
use App\Models\Merchandise\Range;
use App\Models\Merchandise\Tag;
use App\Models\XmlInco;
use App\Traits\Controllers\UpdateModelStatus;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Kwaadpepper\ImageResizer\ImageResizer;
class ProductController extends Controller
{
use UpdateModelStatus;
/**
* If true the index action will only display starred products
* @var false
*/
private $indexModeStarredOnly = false;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $products */
$products = Product::query();
if ($request->search) {
$this->searchQuery(
$products,
$request->search,
null,
'name',
'description',
'reference'
);
} //end if
if ($request->range) {
$products->whereHas('range', function (Builder $q) use ($request) {
$q->where('id', $request->range);
});
}
if ($request->type) {
$products->where('type', $request->type);
}
if ($indexModeStarredOnly = $this->indexModeStarredOnly ? true : false) {
$products->where('star', true);
}
if ($request->orderable) {
$products->where('orderable', true);
}
if ($request->star) {
$products->where('star', true);
}
$this->sortQuery($products);
$searchFields = trans('Nom, Description, Réf');
$products->orderBy('updated_at', 'DESC');
$products->with(['range']);
$products = $products->paginate();
$ranges = Range::all();
$productTypes = Product::fetchTypes();
return view('back::pages.products.index', compact(
'indexModeStarredOnly',
'products',
'ranges',
'searchFields',
'productTypes'
));
}
/**
* Display a listing of the resource.
*
* * Index des produits mis en avant
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function starred(Request $request)
{
$this->indexModeStarredOnly = true;
return $this->callAction('index', [$request]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Http\Requests\Bo\Products\CreateProductRequest $request
* @return \Illuminate\Http\Response
*/
public function create(CreateProductRequest $request)
{
/** @var array product type Minicrud config */
$minicrudData = Product::fetchTypesInputs()[$request->get('type')];
$ranges = Range::all();
$tags = Tag::all();
$existingProds = Product::all();
$productTypes = Product::fetchTypes();
return view('back::pages.products.create', \compact(
'minicrudData',
'productTypes',
'existingProds',
'ranges',
'tags'
));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Products\StoreProductRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreProductRequest $request)
{
return DB::transaction(function () use ($request) {
$product = new Product();
// * Minicrud fill
/** @var array product type Minicrud config */
$minicrudData = Product::fetchTypesInputs()[$request->type];
$product->fill(array_replace_recursive(
\json_decode(\json_encode((object)[
'data' => $minicrudData
]), true),
$request->validated()
));
if ($product->saveOrFail()) {
$product->tags()->sync($request->tag_list);
$product->associatedProducts()->sync($request->product_list);
$product->saveOrFail();
return redirect()
->route('bo.products.edit', $product)
->with('bo.success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.products'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Merchandise\Product $product
* @return \Illuminate\Http\Response
*/
public function edit(Product $product)
{
$ranges = Range::all();
$tags = Tag::all();
/** @var \Illuminate\Database\Eloquent\Collection<mixed,\App\Models\Merchandise\Product> */
$selectedProds = $product->associatedProducts;
/** @var \Illuminate\Database\Eloquent\Collection<mixed,\App\Models\Merchandise\Product> */
$existingProds = Product::query()->where('id', '!=', $product->id)->get();
$productTypes = Product::fetchTypes();
return view('back::pages.products.edit', [
'productModel' => $product,
'ranges' => $ranges,
'tags' => $tags,
'productTypes' => $productTypes,
'existingProds' => $existingProds,
'selectedProds' => $selectedProds
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Products\UpdateProductRequest $request
* @param \App\Models\Merchandise\Product $product
* @return \Illuminate\Http\Response
*/
public function update(UpdateProductRequest $request, Product $product)
{
return DB::transaction(function () use ($request, $product) {
// * Minicrud fill
$product->fill(array_replace_recursive(
\json_decode(\json_encode($product->toArray()), true),
$request->validated()
));
$product->makeStatusSense();
$product->makeOrderableSense();
$product->tags()->sync($request->tag_list);
$product->associatedProducts()->sync($request->product_list);
if ($product->saveOrFail()) {
return redirect()
->route('bo.products.edit', $product)
->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.products'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Merchandise\Product $product
* @return \Illuminate\Http\Response
*/
public function destroy(Product $product)
{
if ($product->deleteOrFail()) {
return redirect()
->route('bo.products.index')
->with('bo.warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.products'))]
));
}
return back();
}
/**
* Associate XmlInco with product
*
* @param \App\Models\Merchandise\Product $product
* @param \App\Models\XmlInco $xml_inco
* @return \Illuminate\Http\RedirectResponse
*/
public function associateXmlInco(Product $product, XmlInco $xml_inco)
{
// * Allow if auth, request post and product type is inco
Gate::allowIf(request()->isMethod('post'));
$this->authorize('associateToInco', $product);
$product->makeStatusSense();
$product->xmlInco()->associate($xml_inco);
$product->saveOrFail();
return redirect()->route('bo.products.edit', $product)
->with('bo.success', trans(
':model est associé avec fiche inco \':incoId\' \':descriptif\'',
[
'model' => Str::singular(trans('models.classes.slides')),
'incoId' => $xml_inco->id,
'descriptif' => Str::limit($xml_inco->descriptif)
]
));
}
/**
* Resize an inco picture and save to to the concerned product
*
* @param \App\Models\Merchandise\Product $product
* @param \App\Models\XmlInco $xml_inco
* @param integer $picture_index
* @return \Illuminate\Http\RedirectResponse
* @throws \RuntimeException If anthing goes wrong managing disks files.
* @throws \Kwaadpepper\ImageResizer\Exceptions\ImageResizerException If resizing the picture fails.
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public function copyXmlIncoPicture(Product $product, XmlInco $xml_inco, int $picture_index): RedirectResponse
{
// * Check product is associated.
if (!$product->xmlInco) {
redirect()->route('bo.products.edit', $product)
->withErrors(['xmlInco' => 'Vous devez d\'abord associer une fiche Inco']);
}
// * Check product inco is the same as requested.
if ($product->xmlInco->id !== $xml_inco->id) {
redirect()->route('bo.products.edit', $product)
->withErrors(['xmlInco' => 'La fiche inco associé ne correspond pas à l\'image']);
}
// * Allow if auth, request post and product type is inco and product has inco media on wanted index.
Gate::allowIf(function ($user) use ($product, $xml_inco, $picture_index) {
return $user and request()->isMethod('post') and
$product->hasAndIsInco() and $xml_inco->mediaList->has($picture_index);
});
/** @var array<string,string> Indexes are filename and imgdata */
$picture = $xml_inco->mediaList->get($picture_index);
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storagePublicDisk = Storage::disk('public');
$pictureBaseName = $picture['file_name'];
$pictureExtension = \pathinfo($picture['file_name'], \PATHINFO_EXTENSION);
/**
* We create one file in tmp to be resized
* then use resize image to get cached file that we will copy.
*/
// * Make sys tmp file.
if (!($file = tmpfile())) {
throw new \RuntimeException('Failed to create tmp file handle');
}
// * Wrap in try catch to always close tmp file
try {
// * Write picutre on tmp file.
$path = stream_get_meta_data($file)['uri'];
if (!File::put($path, $picture['imgdata'])) {
throw new \RuntimeException('Failed to write content on tmp file');
}
// * Assert theme config is present.
if (
!config()->has('theme.models.product.picture_maxheight') or
!config()->has('theme.models.product.picture_maxwidth')
) {
throw new \RuntimeException('config theme.models.product needs picture_maxheight and picture_maxwidth');
}
// * Dynamic push config.
\config()->set('image-resizer.templates.incoPictureResizeForProduct', [
'height' => config()->get('theme.models.product.picture_maxheight', 235),
'width' => config()->get('theme.models.product.picture_maxwidth', 235),
'format' => $pictureExtension,
'inCanvas' => true,
'resize' => true,
'keepRatio' => true
]);
// * Resize picture using kwaadpepper/image-resizer.
$resizedPath = ImageResizer::resizeImageOrIgnore(
$path,
'incoPictureResizeForProduct'
);
if (!$storagePublicDisk->has($resizedPath)) {
throw new \RuntimeException("Resized picture '$resizedPath' does not exists on 'public' disk");
}
// * Append unique prefix to File original basename
$pictureBaseNewName = Str::uuid() . '__' . $pictureBaseName;
// * Rename picture.
$newPathName = \pathinfo($resizedPath, \PATHINFO_DIRNAME) . "/{$pictureBaseNewName}";
if (!$storagePublicDisk->move($resizedPath, $newPathName)) {
throw new \RuntimeException("Failed to rename '$resizedPath' into '$newPathName'");
}
$product->picture = new \SplFileInfo($storagePublicDisk->path($newPathName));
$product->saveOrFail();
// * remove tmp cached file.
$storagePublicDisk->delete($newPathName);
} finally {
fclose($file);
} //end try
return redirect()->route('bo.products.edit', $product);
}
}

View file

@ -1,838 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\ProductRequest;
use App\Models\OldSlug;
use App\Models\FoodProduct;
use App\Models\ToolProduct;
use App\Models\AnifoodProduct;
use App\Models\GarmentProduct;
use App\Models\Product;
use App\Models\Range;
use App\Models\Tag;
use App\Models\TaxRate;
use App\Models\XmlInco;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;
class ProductsController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$ranges = Range::all();
$filter_range = $request->query('filter_range');
$filter_name = $request->query('filter_name');
$products = new Product();
if (!empty($filter_name)) {
$products = $products->where('name', 'like', '%' . $filter_name . '%');
}
if (!empty($filter_range)) {
$products = $products->where('range_id', $filter_range);
}
$products = $products->paginate(10);
// Ici récupération des infos additionnelles pour affichage dans la liste.
foreach ($products as &$product) {
if (!empty($product->product_type)) {
$product->additionnalProductInfo = $product->additionnalProductInfo($product->product_type);
if ($product->additionnalProductInfo === null) {
// N'existe pas on renseigne un vide.
$product_type = $product->product_type;
$modelClass = 'App\Models\\' . ucfirst($product_type);
$product->$product_type = new $modelClass();
}
}
}
unset($product);
return view('bo.products.index', compact('products', 'ranges', 'filter_name', 'filter_range'));
}
/**
* Show the form for creating a new resource.
* @param string $product_type
* @return \Illuminate\Http\Response
*/
public function create(string $product_type = null)
{
if (!empty($product_type) && !array_key_exists($product_type, config('theme.enum_product_types'))) {
abort(404, "Cette page n'existe pas.");
}
$modelClass = 'App\Models\\' . ucfirst($product_type);
$product = new Product();
if (!empty($product_type)) {
$product->$product_type = new $modelClass();
}
$ranges = Range::all();
$taxes = TaxRate::all();
return view('bo.products.create', compact('product_type', 'product', 'ranges', 'taxes'));
}
/**
* store a new product
*
* @param ProductRequest $request
* @return \Illuminate\Http\Response
*/
public function store(ProductRequest $request)
{
// Products fields.
$name = $request->name;
$description = $request->description;
$picture = $request->picture;
$picture_large = $request->picture_large;
$price = $request->price;
$invoicing_unit = $request->invoicing_unit;
$priceBuy = $request->price_buy;
$order_unit = $request->order_unit;
$stock = $request->stock;
$reference = $request->reference;
$sell_unit = $request->sell_unit;
$weight = $request->weight;
$weight_unit = $request->weight_unit;
$status = $request->status === null ? 0 : 1;
$orderable_start = $request->orderable_start_date === null ? null
: $request->orderable_start_date . " " . $request->orderable_start_time;
$orderable_end = $request->orderable_end_date === null ? null
: $request->orderable_end_date . " " . $request->orderable_end_time;
$star = $request->star === null ? 0 : 1;
$slug = Str::slug($name);
$rangeId = $request->range_id;
$taxRateId = $request->tax_rate_id;
// Check si le slug existe déjà dans cette gamme de produits.
$slugExist = Product::where([
['range_id', $rangeId],
['slug', $slug]
])->first();
if ($slugExist) {
return redirect()->back()
->withInput($request->input())
->withErrors("Erreur : un produit avec le slug d'accès \"" . $slug .
"\" éxiste déjà. Veuillez changer le nom du produit.");
}
$product = new Product();
$product->name = $name;
$product->description = $description;
$product->price = $price;
$product->invoicing_unit = $invoicing_unit;
$product->price_buy = $priceBuy;
$product->order_unit = $order_unit;
$product->stock = $stock;
$product->reference = $reference;
$product->sell_unit = $sell_unit;
$product->weight = $weight;
$product->weight_unit = $weight_unit;
$product->slug = $slug;
$product->status = $status;
$product->orderable_start = $orderable_start;
$product->orderable_end = $orderable_end;
$product->star = $star;
$product->range_id = $rangeId;
$product->tax_rate_id = $taxRateId;
$product->save();
// Check si le slug existe dans la table old_slugs, auquel cas il est attribué a ce nouveau produit.
$oldSlug = OldSlug::where('slug', $slug)->first();
if ($oldSlug && $oldSlug->product_id !== $product->id) {
$oldSlug->product_id = $product->id;
$oldSlug->save();
}
$str_random = Str::random(5);
if ($picture !== null) {
$imageName = $product->id . '_' . $str_random . '.' . $picture->extension();
$picture->move(public_path('images/produits'), $imageName);
$product->picture = $imageName;
$product->save();
}
if ($picture_large !== null) {
$imageName = $product->id . '_' . $str_random . '_large.' . $picture_large->extension();
$picture_large->move(public_path('images/produits'), $imageName);
$product->picture_large = $imageName;
$product->save();
}
// Ajout des infos additionnelles.
$product_type = $request->product_type;
if ($product_type !== null) {
$product->product_type = $product_type;
$product->save();
$methodName = "save" . ucfirst($product_type);
$this->$methodName($request, $product->id);
}
// Additionnal product info (foodProduct).
return redirect()->route('bo.products.index');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$product = Product::findOrFail($id);
if (!empty($product->product_type)) {
$product->additionnalProductInfo = $product->additionnalProductInfo($product->product_type);
/**
* Above lines equivalent to
* $product = Product::with('additionnalProductInfo')->findOrFail($id);
*/
if ($product->additionnalProductInfo === null) {
// N'existe pas on renseigne un vide.
$product_type = $product->product_type;
$modelClass = 'App\Models\\' . ucfirst($product_type);
$product->$product_type = new $modelClass();
}
}
$ranges = Range::all();
$taxes = TaxRate::all();
$existingTags = Tag::all();
$selectedtags = $product->tags;
$existingProds = Product::all();
$selectedProds = $product->products;
$existingProds = $existingProds->diff($selectedProds);
return view('bo.products.edit', compact(
'product',
'ranges',
'taxes',
'existingTags',
'selectedtags',
'existingProds',
'selectedProds'
));
}
/**
* Update the specified resource.
*
* @param \App\Models\Product $product
* @param ProductRequest $request
* @return \Illuminate\Http\Response
*/
public function update(Product $product, ProductRequest $request)
{
$orderable_end_time = $request->orderable_end_time === null ? "23:59" : $request->orderable_end_time;
$name = $request->name;
$description = $request->description;
$price = $request->price;
$invoicing_unit = $request->invoicing_unit;
$priceBuy = $request->price_buy;
$order_unit = $request->order_unit;
$stock = $request->stock;
$reference = $request->reference;
$sell_unit = $request->sell_unit;
$weight = $request->weight;
$weight_unit = $request->weight_unit;
$status = $request->status === null ? 0 : 1;
$orderable_start = $request->orderable_start_date === null ? null
: $request->orderable_start_date . " " . $request->orderable_start_time;
$orderable_end = $request->orderable_end_date === null ? null
: $request->orderable_end_date . " " . $orderable_end_time;
$star = $request->star === null ? 0 : 1;
$slug = Str::slug($name);
$rangeId = $request->range_id;
$taxRateId = $request->tax_rate_id;
$this->checkSlugExistsWithinRange($request, $rangeId, $slug, $product->id);
$this->manageProductImages($request->picture, $product, $request->picture_large);
$product->name = $name;
$this->saveNewSlugForProduct($product, $slug);
$product->description = $description;
$product->price = $price;
$product->invoicing_unit = $invoicing_unit;
$product->price_buy = $priceBuy;
$product->order_unit = $order_unit;
$product->stock = $stock;
$product->reference = $reference;
$product->sell_unit = $sell_unit;
$product->weight = $weight;
$product->weight_unit = $weight_unit;
$product->slug = $slug;
$product->status = $status;
$product->orderable_start = $orderable_start;
$product->orderable_end = $orderable_end;
$product->star = $star;
$product->range_id = $rangeId;
$product->tax_rate_id = $taxRateId;
$product->save();
// Check si le slug existe dans la table old_slug, auquel cas il est attribué a ce nouveau produit.
$this->cleanOldSlug($slug);
// Ajout des infos additionnelles.
$product_type = $request->product_type;
// Save additionnal.
if ($product_type !== null) {
$methodName = "save" . ucfirst($product_type);
$this->$methodName($request, $product->id);
}
// Tags associés.
$this->syncTags($request->tags, $product);
// Produits associés.
$this->syncProduitsAssocies($request->prods, $product);
return redirect()->route('bo.products.edit', $product->id)->with('success', 'Modifications enregistrés !');
}
/**
* Check if slug already exists, if so redirect
*
* @param Request $request
* @param integer $rangeId
* @param string $slug
* @param integer|null $productId
* @return \Illuminate\Http\RedirectResponse|void
*/
private function checkSlugExistsWithinRange(Request $request, int $rangeId, string $slug, int $productId = null)
{
// Check si le slug existe déjà dans cette gamme de produits.
$slugExist = Product::where([
['range_id', $rangeId],
['slug', $slug],
['id', '<>', $productId]
])->first();
if ($slugExist) {
return redirect()->back()
->withInput($request->input())
->withErrors("Erreur : un produit avec le slug d'accès \"" . $slug .
"\" éxiste déjà. Veuillez changer le nom du produit.");
}
}
/**
* If new slug for product then save oldslug
*
* @param Product $product
* @param string $slug
* @return void
*/
private function saveNewSlugForProduct(Product $product, string $slug)
{
if ($product->slug != $slug) {
$exist = OldSlug::where('slug', $product->slug)->first();
if ($exist === null) {
$oldSlug = new OldSlug();
$oldSlug->product_id = $product->id;
$oldSlug->slug = $product->slug;
$oldSlug->save();
}
}
}
/**
* clean oldslug if exists
*
* @param string $slug
* @return void
*/
private function cleanOldSlug(string $slug)
{
// Check si le slug existe dans la table old_slug, auquel cas il est attribué a ce nouveau produit.
$oldSlug = OldSlug::where('slug', $slug)->first();
if ($oldSlug) {
$oldSlug->delete();
}
}
/**
* Store and update saved images on product
*
* @param UploadedFile|null $picture
* @param Product $product
* @param UploadedFile|null $picture_large
* @return void
*/
private function manageProductImages($picture, Product &$product, $picture_large)
{
$str_random = Str::random(5);
if ($picture !== null) {
if (
$product->picture !== null &&
file_exists(public_path('images/produits/') . $product->picture)
) {
unlink(public_path('images/produits/') . $product->picture);
}
$imageName = $product->id . '_' . $str_random . '.' . $picture->extension();
$picture->move(public_path('images/produits'), $imageName);
$product->picture = $imageName;
}
if ($picture_large !== null) {
if (
$product->picture_large !== null &&
file_exists(public_path('images/produits/') . $product->picture_large)
) {
unlink(public_path('images/produits/') . $product->picture_large);
}
$imageName = $product->id . '_' . $str_random . '_large.' . $picture_large->extension();
$picture_large->move(public_path('images/produits'), $imageName);
$product->picture_large = $imageName;
}
}
/**
* sync tags to product
*
* @param array|null $tags
* @param Product $product
* @return void
*/
private function syncTags(array $tags = null, Product &$product)
{
if (!empty($tags)) {
$arTagIds = array();
foreach ($tags as $tag) {
$oTag = json_decode($tag);
if (empty($oTag->id)) {
// We need a new tag.
$arTagIds[] = $this->storeTag($oTag);
} else {
$arTagIds[] = $oTag->id;
}
}
// Prise en charge de la liaison avec les produits.
$product->tags()->sync($arTagIds);
} else {
$product->tags()->sync(array());
}
}
/**
* sync associated products to product
*
* @param string|null $prods
* @param Product $product
* @return void
*/
private function syncProduitsAssocies(string $prods = null, Product $product)
{
if (!empty($prods)) {
$productIds = explode(",", $prods);
// Prise en charge de la liaison avec les produits.
$product->products()->sync($productIds);
} else {
$product->products()->sync(array());
}
}
/**
* Helper method to store a new tag if necessary
* Otherwise retreive an existing tag to associate its id
*
* @param \App\Models\Tag $oTag
* @return integer
*/
public function storeTag(Tag $oTag)
{
$slug = Str::slug($oTag->label);
$tag = Tag::where('slug', $slug)->first();
if ($tag === null) {
// Si on pas encore ce tag on le crée.
$tag = new Tag();
$tag->label = $oTag->label;
$tag->slug = Str::slug($oTag->label);
// A new tag is published by default.
$tag->status = 1;
$tag->order = TagsController::getLastOrder();
$tag->save();
}
return $tag->id;
}
/**
* destroy a product (soft delete)
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$product = Product::findOrFail($id);
if ($product->picture !== null && file_exists(public_path('images/produits/') . $product->picture)) {
unlink(public_path('images/produits/') . $product->picture);
}
if (
$product->picture_large !== null &&
file_exists(public_path('images/produits/') . $product->picture_large)
) {
unlink(public_path('images/produits/') . $product->picture_large);
}
OldSlug::where('product_id', $product->id)->delete();
$product->delete();
return redirect()->route('bo.products.index');
}
/**
* check if inco data exists
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function checkIncoData(Request $request)
{
$id = $request->id;
$xmlInco = XmlInco::find($id);
if (!empty($xmlInco)) {
// We have data.
$hasInco = __("Des données Inco sont disponibles, souhaitez vous les associer à ce produit ?");
$preparedImages = $this->prepareIncoImages($xmlInco->picture);
return view('bo.products.load-inco', compact('hasInco', 'xmlInco', 'preparedImages'));
} else {
return array("data" => array());
}
}
/**
* Show prepared images
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*/
public function getPrepareIncoImages(Request $request)
{
$picture = $request->picture;
$preparedImages = $this->prepareIncoImages($picture);
return view('bo.products.inco-images', compact('preparedImages'));
}
/**
* prepared images
*
* @param array $picture
* @return \Illuminate\Contracts\View\View
*/
public function prepareIncoImages(array $picture)
{
$preparedImages = array();
foreach ($picture as $incoImage) {
// Charge.
if (File::exists(storage_path($incoImage))) {
$imagePath = storage_path($incoImage);
// Redimensionne.
// Create mask.
// Resize original image into mask.
$this->resizeImg(
$imagePath,
config('theme.product_image_large_maxwidth'),
config('theme.product_image_large_maxheight'),
File::name($incoImage) . "_large.png"
);
$this->resizeImg(
$imagePath,
config('theme.product_image_maxwidth'),
config('theme.product_image_maxheight'),
File::name($incoImage) . "_small.png"
);
$preparedImages[] = "inco/images/" . File::name($incoImage) . "_large.png";
}//end if
}//end foreach
return $preparedImages;
}
/**
* redimensionne une image dans un masque
*/
/**
* redimensionne une image dans un masque
*
* @param string $imagePath
* @param integer $maskWidth
* @param integer $maskHeight
* @param string $resizedName
* @return boolean|void
*/
private function resizeImg(string $imagePath, int $maskWidth, int $maskHeight, string $resizedName)
{
// ---- création du masque
$mask = imagecreatetruecolor($maskWidth, $maskHeight);
imagesavealpha($mask, true);
$color = imagecolorallocatealpha($mask, 0, 0, 0, 127);
imagefill($mask, 0, 0, $color);
// ---- Récupère le mime type de l'image
$info = getimagesize($imagePath);
$mime = $info['mime'];
// ---- Défini les fonctions à appeler selon le type de fichier
switch ($mime) {
case 'image/jpeg':
$image_create_func = 'imagecreatefromjpeg';
break;
case 'image/png':
$image_create_func = 'imagecreatefrompng';
break;
default:
return false;
}
// ---- Chargement de l'image source
$img = $image_create_func($imagePath);
list($imgWidth, $imgHeight) = getimagesize($imagePath);
// ---- Calcul les nouvelles dimensions de l'image
if ($imgWidth > $imgHeight) {
$finalWidth = $maskWidth;
$finalHeight = ($finalWidth * $imgHeight) / $imgWidth;
} else {
$finalHeight = $maskHeight;
$finalWidth = ($finalHeight * $imgWidth) / $imgHeight;
}
// ---- Redimensionne l'image source
$resizeImg = imagecreatetruecolor($finalWidth, $finalHeight);
imagealphablending($resizeImg, false);
imagesavealpha($resizeImg, true);
imagecopyresampled($resizeImg, $img, 0, 0, 0, 0, $finalWidth, $finalHeight, $imgWidth, $imgHeight);
// Copie l'image redimensionnée dans le mask.
imagecopy(
$mask,
$resizeImg,
($maskWidth - $finalWidth) / 2,
($maskHeight - $finalHeight) / 2,
0,
0,
imagesx($resizeImg),
imagesy($resizeImg)
);
imagealphablending($mask, false);
imagesavealpha($mask, true);
// Ecrit dans storage/app/public.
// Créer le fichier final.
imagepng($mask, storage_path('app/public/inco/images/' . $resizedName), 5);
// Libère la mémoire associée aux images manipulées.
imagedestroy($img);
imagedestroy($resizeImg);
imagedestroy($mask);
}
/**
* set une image préparée comme image du produit
*/
/**
* set une image préparée comme image du produit
*
* @param Request $request
* @return \App\Models\Product
*/
public function setIncoImage(Request $request)
{
$imagePath = $request->picture;
$imagePath = storage_path("app/public/" . $imagePath);
$product_id = $request->product;
$product = Product::findOrFail($product_id);
$str_random = Str::random(5);
// Default is large image.
$finalImageName = $product->id . '_' . $str_random . '_large.' . File::extension($imagePath);
$productimagespath = public_path('images/produits');
if (!File::isDirectory($productimagespath)) {
File::makeDirectory($productimagespath, 0644, true);
}
File::copy($imagePath, $productimagespath . "/" . $finalImageName);
// Suppression de l'ancien fichier.
if (
$product->picture_large !== null &&
file_exists(public_path('images/produits/') . $product->picture_large)
) {
unlink(public_path('images/produits/') . $product->picture_large);
}
$product->picture_large = $finalImageName;
// Now set small image.
$imagePath = str_replace("_large", "_small", $imagePath);
$finalImageName = $product->id . '_' . $str_random . '.' . File::extension($imagePath);
File::copy($imagePath, $productimagespath . "/" . $finalImageName);
// Suppression de l'ancien fichier.
if ($product->picture !== null && file_exists(public_path('images/produits/') . $product->picture)) {
unlink(public_path('images/produits/') . $product->picture);
}
$product->picture = $finalImageName;
$product->save();
return $product;
}
/**
* helper method to update additionnal product info
*
* @param Request $request
* @param integer $product_id
* @return void
*/
public function saveFoodProduct(Request $request, int $product_id)
{
$foodProduct = FoodProduct::firstOrNew([
'product_id' => $product_id,
]);
if (!empty($request->no_art)) {
$foodProduct->no_art = $request->no_art;
}
$foodProduct->ingredients = $request->ingredients;
$foodProduct->preparation = $request->preparation;
$foodProduct->servings = $request->servings;
$foodProduct->allergens = $request->allergens;
$foodProduct->selling_argument = $request->selling_argument;
$foodProduct->nv_ref_weight_g = $request->nv_ref_weight_g;
$foodProduct->nv_kj = $request->nv_kj;
$foodProduct->nv_kcal = $request->nv_kcal;
$foodProduct->nv_fat_g = $request->nv_fat_g;
$foodProduct->nv_sat_fat_g = $request->nv_sat_fat_g;
$foodProduct->nv_carb_g = $request->nv_carb_g;
$foodProduct->nv_sugar_g = $request->nv_sugar_g;
$foodProduct->nv_fiber_g = $request->nv_fiber_g;
$foodProduct->nv_protein_g = $request->nv_protein_g;
$foodProduct->nv_salt_g = $request->nv_salt_g;
$foodProduct->nv_serving_size_g = $request->nv_serving_size_g;
$foodProduct->nv_serving_kj = $request->nv_serving_kj;
$foodProduct->nv_serving_kcal = $request->nv_serving_kcal;
$foodProduct->nv_serving_fat_g = $request->nv_serving_fat_g;
$foodProduct->nv_serving_sat_fat_g = $request->nv_serving_sat_fat_g;
$foodProduct->nv_serving_carb_g = $request->nv_serving_carb_g;
$foodProduct->nv_serving_sugar_g = $request->nv_serving_sugar_g;
$foodProduct->nv_serving_fiber_g = $request->nv_serving_fiber_g;
$foodProduct->nv_serving_protein_g = $request->nv_serving_protein_g;
$foodProduct->nv_serving_salt_g = $request->nv_serving_salt_g;
$foodProduct->nv_daily_energy = $request->nv_daily_energy;
$foodProduct->nv_daily_fat = $request->nv_daily_fat;
$foodProduct->nv_daily_sat_fat = $request->nv_daily_sat_fat;
$foodProduct->nv_daily_carb = $request->nv_daily_carb;
$foodProduct->nv_daily_sugar = $request->nv_daily_sugar;
$foodProduct->nv_daily_fiber = $request->nv_daily_fiber;
$foodProduct->nv_daily_protein = $request->nv_daily_protein;
$foodProduct->nv_daily_salt = $request->nv_daily_salt;
$foodProduct->nutriscore = $request->nutriscore;
$foodProduct->nutriscore_args = $request->nutriscore_args;
$foodProduct->product_id = $product_id;
$foodProduct->save();
}
/**
* helper method to update toolproduct info
*
* @param Request $request
* @param integer $product_id
* @return void
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function saveToolProduct(Request $request, int $product_id)
{
// phpcs:enable
$toolProduct = ToolProduct::firstOrNew([
'product_id' => $product_id,
]);
$toolProduct->save();
}
/**
* helper method to update anifoodProduct info
*
* @param Request $request
* @param integer $product_id
* @return void
*/
public function saveAnifoodProduct(Request $request, int $product_id)
{
$anifoodProduct = AnifoodProduct::firstOrNew([
'product_id' => $product_id,
]);
$anifoodProduct->composition = $request->composition;
$anifoodProduct->servings = $request->servings;
$anifoodProduct->save();
}
/**
* helper method to update garmentProduct info
*
* @param Request $request
* @param integer $product_id
* @return void
*/
public function saveGarmentProduct(Request $request, int $product_id)
{
$garmentProduct = GarmentProduct::firstOrNew([
'product_id' => $product_id,
]);
$garmentProduct->size = $request->size;
$garmentProduct->color = $request->color;
$garmentProduct->material = $request->material;
$garmentProduct->save();
}
/**
* index des produits mis en avant
*
* @return * @return \Illuminate\Contracts\View\View
*/
public function starred()
{
$products = Product::where('star', 1)->paginate(10);
return view('bo.products.starred', compact('products'));
}
}

View file

@ -1,139 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\DiscountType;
use App\Models\Promotion;
use App\Models\Product;
use Illuminate\Http\Request;
class PromotionsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$promotions = Promotion::all();
return view('bo.promotions.index', compact('promotions'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$discTypes = DiscountType::all();
$existingProds = Product::all();
$promotion = new Promotion();
$selectedProds = $promotion->products;
return view('bo.promotions.create', compact('discTypes', 'existingProds', 'selectedProds'));
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'product_id' => 'required',
]);
$productIds = explode(",", $request->product_id);
$discType = $request->discount_type_id;
$value = $request->discount_value;
$date_start = $request->date_start_date . " " . $request->date_start_time;
$date_end = $request->date_end_date . " " . $request->date_end_time;
$promotion = new Promotion();
$promotion->discount_type_id = $discType;
$promotion->discount_value = $value;
$promotion->date_start = $date_start;
$promotion->date_end = $date_end;
$promotion->save();
// Prise en charge de la liaison avec les produits.
$promotion->products()->sync($productIds);
return redirect()->route('bo.promotions.index');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$promotion = Promotion::findOrFail($id);
$discTypes = DiscountType::all();
$existingProds = Product::all();
$selectedProds = $promotion->products;
$existingProds = $existingProds->diff($selectedProds);
return view('bo.promotions.edit', compact('promotion', 'discTypes', 'existingProds', 'selectedProds'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$promotion = Promotion::findOrFail($id);
$request->validate([
'product_id' => 'required',
]);
$productIds = explode(",", $request->product_id);
$discType = $request->discount_type_id;
$value = $request->discount_value;
$date_start = $request->date_start_date . " " . $request->date_start_time;
$date_end = $request->date_end_date . " " . $request->date_end_time;
$promotion->discount_type_id = $discType;
$promotion->discount_value = $value;
$promotion->date_start = $date_start;
$promotion->date_end = $date_end;
$promotion->save();
// Prise en charge de la liaison avec les produits.
$promotion->products()->sync($productIds);
return redirect()->route('bo.promotions.edit', $promotion->id)->with('success', 'Modifications enregistrés !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$p = Promotion::findOrFail($id);
// Prise en charge de la liaison avec les produits.
$p->products()->detach();
$p->delete();
return redirect()->route('bo.promotions.index');
}
}

View file

@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Ranges\StoreRangeRequest;
use App\Http\Requests\Bo\Ranges\UpdateRangeRequest;
use App\Models\Merchandise\Range;
use App\Traits\Controllers\UpdateModelStatus;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class RangeController extends Controller
{
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $ranges */
$ranges = Range::query();
if ($request->search) {
$this->searchQuery(
$ranges,
$request->search,
null,
'name',
'description'
);
}
$this->sortQuery($ranges);
$searchFields = trans('Nom, Description');
$ranges->orderBy('updated_at', 'DESC');
$ranges = $ranges->paginate();
return view('back::pages.ranges.index', compact('ranges', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('back::pages.ranges.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Ranges\StoreRangeRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreRangeRequest $request)
{
return DB::transaction(function () use ($request) {
$range = new Range();
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
->with('bo.success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.ranges'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Merchandise\Range $range
* @return \Illuminate\Http\Response
*/
public function edit(Range $range)
{
return view('back::pages.ranges.edit', [
'rangeModel' => $range
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Ranges\UpdateRangeRequest $request
* @param \App\Models\Merchandise\Range $range
* @return \Illuminate\Http\Response
*/
public function update(UpdateRangeRequest $request, Range $range)
{
return DB::transaction(function () use ($request, $range) {
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.ranges'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Merchandise\Range $range
* @return \Illuminate\Http\Response
*/
public function destroy(Range $range)
{
if ($range->products()->count() > 0) {
return redirect()->back()
->withErrors("Suppression impossible : {$range->id} {$range->name} contient encore des produits.");
}
if ($range->deleteOrFail()) {
return redirect()
->route('bo.ranges.index')
->with('bo.warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.ranges'))]
));
}
return back();
}
}

View file

@ -1,169 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\OldSlug;
use App\Models\Product;
use App\Models\Range;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class RangesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$ranges = Range::all();
return view('bo.ranges.index', compact('ranges'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.ranges.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$name = $request->name;
$slug = Str::slug($name);
$description = $request->description;
$picture = $request->picture;
$status = $request->status === null ? 0 : 1;
$request->validate([
'name' => 'required|string|max:255',
'description' => 'required',
'picture' => 'image|mimes:jpeg,png,jpg|max:2048|dimensions:max-width=' .
config('theme.range_image_maxwidth') . ',max-height=' .
config('theme.range_image_maxheight'),
]);
$range = new Range();
$range->name = $name;
$range->slug = $slug;
$range->description = $description;
$range->status = $status;
$range->save();
if ($picture !== null) {
$imageName = $range->id . '_' . $slug . '_' . Str::random(5) . '.' . $picture->extension();
$picture->move(public_path('images/gammes'), $imageName);
$range->picture = $imageName;
$range->save();
}
return redirect()->route('bo.ranges.index');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$range = Range::findOrFail($id);
return view('bo.ranges.edit', compact('range'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$name = $request->name;
$slug = Str::slug($name);
$description = $request->description;
$picture = $request->picture;
$status = $request->status === null ? 0 : 1;
$request->validate([
'name' => 'required|string|max:255',
'description' => 'required',
'picture' => 'image|mimes:jpeg,png,jpg|max:2048|dimensions:max-width=' .
config('theme.range_image_maxwidth') . ',max-height=' .
config('theme.range_image_maxheight'),
]);
$range = Range::findOrFail($id);
if ($picture !== null) {
if ($range->picture !== null && file_exists(public_path('images/gammes/') . $range->picture)) {
unlink(public_path('images/gammes/') . $range->picture);
}
$imageName = $range->id . '_' . $slug . '_' . Str::random(5) . '.' . $picture->extension();
$picture->move(public_path('images/gammes'), $imageName);
}
$range->name = $name;
if ($range->slug != $slug) {
$exist = OldSlug::where('slug', $range->slug)->first();
if ($exist === null) {
$oldSlug = new OldSlug();
$oldSlug->range_id = $range->id;
$oldSlug->slug = $range->slug;
$oldSlug->save();
}
}
$range->slug = $slug;
$range->description = $description;
if ($picture !== null) {
$range->picture = $imageName;
}
$range->status = $status;
$range->save();
return redirect()->route('bo.ranges.edit', $range->id)->with('success', 'Modifications enregistrés !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$range = Range::findOrFail($id);
$product = Product::where('range_id', $range->id)->count();
if ($product !== 0) {
return redirect()->back()
->withErrors("Suppression impossible : \"" . $range->name . "\" contient encore des produits.");
}
if ($range->picture !== null) {
if (file_exists(public_path('images/gammes/') . $range->picture)) {
unlink(public_path('images/gammes/') . $range->picture);
}
}
OldSlug::where('range_id', $range->id)->delete();
$range->delete();
return redirect()->route('bo.ranges.index');
}
}

View file

@ -1,112 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Config;
class SettingsController extends Controller
{
/**
* Manage settings page
*
* @param Request $request
* @return \Illuminate\Http\Response
*
*/
public function manage(Request $request)
{
$settings = Setting::all();
$settingsToPrint = [];
foreach ($settings as $setting) {
$settingsToPrint[$setting->key] = $setting->value;
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$input = $request->all();
// Comparer les settingsToPrint (en base) avec les inputs (pour trouver un nouveau).
foreach ($input as $key => $value) {
if (
!isset($settingsToPrint[$key])
&& $key != "_method"
&& $key != "_token"
) {
// New setting.
$setting = new Setting();
$setting->key = $key;
$setting->value = $value ?? 0;
$setting->save();
}
}
$looplimit = count($settings);
for ($i = 0; $i < $looplimit; $i++) {
if (is_array($request->input($settings[$i]->key))) {
$settings[$i]->value = implode(',', $request->input($settings[$i]->key));
} else {
$settings[$i]->value = $request->input($settings[$i]->key) === null ? 0
: $request->input($settings[$i]->key);
}
$settings[$i]->save();
}
// Store redirector to keep flash message from being wipe pby artisan call below.
$redirector = redirect()->route('bo.settings')->with('success', 'Modifications enregistrés !');
// All settings saved : cache routes.
Artisan::call('route:clear');
// Programmer un php artisan optimize ? (une fois par jour la nuit par exemple).
return $redirector;
}//end if
return view('bo.settings', compact('settingsToPrint'));
}
/**
* Manage Theme settings page
* Management des settings dont la clé est préfixée theme_
*
* @param Request $request
* @return \Illuminate\Http\Response
*
*/
public function manageTheme(Request $request)
{
$settings = Setting::where('key', 'LIKE', 'theme_%')->get();
$settingsToPrint = [];
foreach ($settings as $setting) {
$settingsToPrint[$setting->key] = $setting->value;
}
$config_settings = Config::get(('theme.bo_theme_settings'));
// Check if, for each of the theme defined settings we have a database setting.
foreach ($config_settings as $onekey => $params) {
if (!isset($settingsToPrint['theme_' . $onekey])) {
$dbsetting = new Setting();
$dbsetting->key = 'theme_' . $onekey;
$dbsetting->value = $params['default_value'];
$dbsetting->save();
// Injection dans settings to print.
$settingsToPrint[$dbsetting->key] = $dbsetting->value;
}
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$looplimit = count($settings);
for ($i = 0; $i < $looplimit; $i++) {
// Attention pour les booléens/checkbox on enregistre 'on'.
$settings[$i]->value = $request->input($settings[$i]->key) === null ? 0
: $request->input($settings[$i]->key);
$settings[$i]->save();
}
return redirect()->route('bo.themesettings')->with('success', 'Modifications enregistrés !');
}
return view('bo.settings.theme', compact('settingsToPrint', 'config_settings'));
}
}

View file

@ -0,0 +1,197 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Ajax\TimeslotExceptions\DeleteTimeslotExceptionsRequest;
use App\Http\Requests\Bo\Ajax\TimeslotExceptions\SaveTimeslotExceptionsRequest;
use App\Http\Requests\Bo\Shops\StoreShopRequest;
use App\Http\Requests\Bo\Shops\UpdateShopRequest;
use App\Models\Shop\CollectionException;
use App\Models\Shop\Shop;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class ShopController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $shops */
$shops = Shop::query();
if ($request->search) {
$this->searchQuery(
$shops,
$request->search,
null,
'name',
'address',
'city',
'postal_code'
);
}
$this->sortQuery($shops);
$searchFields = trans('Nom, adresse, ville, cp');
$shops->orderBy('updated_at', 'DESC');
$shops = $shops->paginate();
return view('back::pages.shops.index', compact('shops', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('back::pages.shops.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Shops \StoreShopRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreShopRequest $request)
{
return DB::transaction(function () use ($request) {
$shop = new Shop();
$shop->fill($request->validated());
if ($shop->saveOrFail()) {
return redirect()
->route('bo.shops.edit', $shop)
->with('bo.success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.shops'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Shop\Shop $shop
* @return \Illuminate\Http\Response
*/
public function edit(Shop $shop)
{
$exceptionsCollecte = $shop->collectionExceptions()->orderBy('date')->get();
return view('back::pages.shops.edit', [
'shopModel' => $shop,
'exceptionsCollecte' => $exceptionsCollecte
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Shops\UpdateShopRequest $request
* @param \App\Models\Shop\Shop $shop
* @return \Illuminate\Http\Response
*/
public function update(UpdateShopRequest $request, Shop $shop)
{
return DB::transaction(function () use ($request, $shop) {
$shop->fill($request->validated());
if ($shop->saveOrFail()) {
return redirect()
->route('bo.shops.edit', $shop)
->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.shops'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Shop\Shop $shop
* @return \Illuminate\Http\Response
*/
public function destroy(Shop $shop)
{
if (Shop::count() <= 1) {
return redirect()->back()
->withErrors('Suppression impossible : Vous devez conserver au moins 1 point de retrait.');
}
if ($shop->deleteOrFail()) {
return redirect()
->route('bo.shops.index')
->with('bo.warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.shops'))]
));
}
return back();
}
/**
* Save collect exceptions
*
* @param \App\Http\Requests\Bo\Ajax\TimeslotExceptions\SaveTimeslotExceptionsRequest $request
* @param \App\Models\Shop\Shop $shop
* @return \Illuminate\Http\JsonResponse
*/
public function saveExceptions(SaveTimeslotExceptionsRequest $request, Shop $shop): JsonResponse
{
return DB::transaction(function () use ($request, $shop) {
$collecteExceptionDates = \collect($request->dates)->keyBy('date');
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Shop\CollectionException> */
$collectionExceptions = $shop->collectionExceptions
->filter(function (CollectionException $collectionException) use ($collecteExceptionDates) {
if (!$collecteExceptionDates->has($collectionException->date->format('Y-m-d'))) {
$collectionException->deleteOrFail();
return false;
}
$collecteExceptionDates->pull($collectionException->date->format('Y-m-d'));
return true;
});
$collecteExceptionDates->map(function (array $ajaxCollectionexception) use ($shop, $collectionExceptions) {
$collectionException = new CollectionException($ajaxCollectionexception);
$collectionException->shop()->associate($shop);
$collectionException->saveOrFail();
$collectionException->refresh();
$collectionExceptions->push($collectionException);
});
return \response()->json($collectionExceptions);
});
}
/**
* delete collecte exception
*
* @param \App\Http\Requests\Bo\Ajax\TimeslotExceptions\DeleteTimeslotExceptionsRequest $request
* @param \App\Models\Shop\Shop $shop
* @return string
*/
public function destroyExceptions(DeleteTimeslotExceptionsRequest $request, Shop $shop): JsonResponse
{
return DB::transaction(function () use ($request, $shop) {
CollectionException::whereHas('shop', function (Builder $q) use ($shop) {
$q->where('id', $shop->id);
})->whereId($request->id)->firstOrFail()->deleteOrFail();
return \response()->json($shop->collectionExceptions);
});
}
}

View file

@ -0,0 +1,136 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Slides\StoreSlideRequest;
use App\Http\Requests\Bo\Slides\UpdateSlideRequest;
use App\Models\Site\Slide;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class SlideController extends Controller
{
use ChangesModelOrder;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $slides */
$slides = Slide::query();
if ($request->search) {
$this->searchQuery(
$slides,
$request->search,
null,
'title'
);
}
$this->sortQuery($slides);
$searchFields = trans('Titre');
$slides->orderBy('updated_at', 'DESC');
$slides = $slides->paginate();
return view('back::pages.slides.index', compact('slides', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('back::pages.slides.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Slides\StoreSlideRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreSlideRequest $request)
{
return DB::transaction(function () use ($request) {
$slide = new Slide();
$slide->fill($request->validated());
$slide->order = Slide::query()->orderBy('order', 'DESC')->limit(1)->value('order') ?? 0;
if ($slide->saveOrFail()) {
return redirect()
->route('bo.slides.edit', $slide)
->with('bo.success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.slides'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Site\Slide $slide
* @return \Illuminate\Http\Response
*/
public function edit(Slide $slide)
{
return view('back::pages.slides.edit', [
'slideModel' => $slide
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Slides\UpdateSlideRequest $request
* @param \App\Models\Site\Slide $slide
* @return \Illuminate\Http\Response
*/
public function update(UpdateSlideRequest $request, Slide $slide)
{
return DB::transaction(function () use ($request, $slide) {
$slide->fill($request->validated());
if ($slide->saveOrFail()) {
return redirect()
->route('bo.slides.edit', $slide)
->with('bo.success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.slides'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Site\Slide $slide
* @return \Illuminate\Http\Response
*/
public function destroy(Slide $slide)
{
if ($slide->deleteOrFail()) {
return redirect()
->route('bo.slides.index')
->with('bo.warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.slides'))]
));
}
return back();
}
}

View file

@ -1,120 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Slider;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class SlidersController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$sliders = Slider::all();
return view('bo.sliders.index', compact('sliders'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.sliders.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$label = $request->label;
$slug = $request->slug;
if (empty($slug)) {
$slug = Str::slug($label);
}
$commentaire = $request->commentaire;
$request->validate([
'label' => 'required|string|max:255',
'slug' => 'sometimes|unique:sliders',
]);
$slider = new Slider();
$slider->label = $label;
$slider->slug = $slug;
$slider->commentaire = $commentaire;
$slider->save();
return redirect()->route('bo.sliders.edit', $slider->id)->with('success', 'Slider créé !');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$slider = Slider::findOrFail($id);
return view('bo.sliders.edit', compact('slider'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$slider = Slider::findOrFail($id);
$label = $request->label;
$slug = $request->slug;
$commentaire = $request->commentaire;
$request->validate([
'label' => 'required|string|max:255',
'slug' => 'required|unique:sliders,slug,' . $slider->id,
]);
$slider->label = $label;
$slider->slug = $slug;
$slider->commentaire = $commentaire;
$slider->save();
return redirect()->route('bo.sliders.edit', $slider->id)->with('success', 'Modifications enregistrées !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$slider = Slider::findOrFail($id);
$count = Slider::count();
if ($count === 1) {
return redirect()->back()->withErrors("Suppression impossible : Vous devez conserver au moins 1 Slider");
}
$slider->delete();
return redirect()->route('bo.sliders.index');
}
}

View file

@ -1,241 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Slide;
use App\Models\Slider;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class SlidesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$slides = Slide::orderBy('order', 'ASC')->get();
return view('bo.slides.index', compact('slides'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$sliders = Slider::all();
return view('bo.slides.create', compact('sliders'));
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$title = $request->title;
$picture = $request->picture;
$text = $request->text;
$linkLabel = $request->link_label;
$link = $request->link;
$newTab = $request->link_new_tab === null ? 0 : 1;
$sliderId = $request->slider_id;
if (empty($request->date_start_date)) {
$date_start = null;
} else {
if (empty($request->date_start_time)) {
$date_start = $request->date_start_date . " 00:00";
} else {
$date_start = $request->date_start_date . " " . $request->date_start_time;
}
}
if (empty($request->date_end_date)) {
$date_end = null;
} else {
if (empty($request->date_end_time)) {
$date_end = $request->date_end_date . " 23:59";
} else {
$date_end = $request->date_end_date . " " . $request->date_end_time;
}
}
$request->validate([
'title' => 'required|max:255',
'picture' => 'image|mimes:jpeg,png,jpg|max:2048|dimensions:max-width=' .
config('theme.slide_image_maxwidth') . ',max-height=' .
config('theme.slide_image_maxheight'),
'link_label' => 'sometimes|max:255',
'link' => 'sometimes|max:255',
]);
$slide = new Slide();
$slide->title = $title;
$slide->text = $text;
$slide->link_label = $linkLabel;
$slide->link = $link;
$slide->link_new_tab = $newTab;
$slide->date_start = $date_start;
$slide->date_end = $date_end;
$slide->slider_id = $sliderId;
$slide->order = $this->getLastOrder();
$slide->save();
if ($picture !== null) {
$imageName = $slide->id . '_' . Str::random(5) . '.' . $picture->extension();
$picture->move(public_path('images/slides'), $imageName);
$slide->picture = $imageName;
$slide->save();
}
return redirect()->route('bo.slides.edit', $slide->id)->with('success', 'Slide créé !');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$slide = Slide::findOrFail($id);
$sliders = Slider::all();
return view('bo.slides.edit', compact('slide', 'sliders'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$title = $request->title;
$picture = $request->picture;
$text = $request->text;
$linkLabel = $request->link_label;
$link = $request->link;
$newTab = $request->link_new_tab === null ? 0 : 1;
$sliderId = $request->slider_id;
if (empty($request->date_start_date)) {
$date_start = null;
} else {
if (empty($request->date_start_time)) {
$date_start = $request->date_start_date . " 00:00";
} else {
$date_start = $request->date_start_date . " " . $request->date_start_time;
}
}
if (empty($request->date_end_date)) {
$date_end = null;
} else {
if (empty($request->date_end_time)) {
$date_end = $request->date_end_date . " 23:59";
} else {
$date_end = $request->date_end_date . " " . $request->date_end_time;
}
}
$request->validate([
'title' => 'required|max:255',
'picture' => 'image|mimes:jpeg,png,jpg|max:2048|dimensions:max-width=' .
config('theme.slide_image_maxwidth') . ',max-height=' .
config('theme.slide_image_maxheight'),
'link_label' => 'sometimes|max:255',
'link' => 'sometimes|max:255',
]);
$slide = Slide::findOrFail($id);
$slide->title = $title;
$slide->text = $text;
$slide->link_label = $linkLabel;
$slide->link = $link;
$slide->link_new_tab = $newTab;
$slide->date_start = $date_start;
$slide->date_end = $date_end;
$slide->slider_id = $sliderId;
$slide->save();
if ($picture !== null) {
if (file_exists(public_path() . 'images/slides/' . $slide->picture)) {
unlink(public_path() . 'images/slides/' . $slide->picture);
}
$imageName = $slide->id . '_' . Str::random(5) . '.' . $picture->extension();
$picture->move(public_path('images/slides'), $imageName);
$slide->picture = $imageName;
$slide->save();
}
return redirect()->route('bo.slides.edit', $slide->id)->with('success', 'Modifications enregistrées !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$s = Slide::findOrFail($id);
$s->delete();
return redirect()->route('bo.slides.index');
}
/**
* Change Slide Order
*
* @param integer $id
* @param string $action
* @return \Illuminate\Http\Response
*/
public function changeOrder(int $id, string $action)
{
$current = Slide::findOrFail($id);
if ($action === 'up') {
$tmp = Slide::where('order', '<', $current->order)->orderBy('order', 'DESC')->first();
$newOrder = $tmp->order;
$tmp->order = $current->order;
$current->order = $newOrder;
} else {
$tmp = Slide::where('order', '>', $current->order)->orderBy('order', 'ASC')->first();
$newOrder = $tmp->order;
$tmp->order = $current->order;
$current->order = $newOrder;
}
$tmp->save();
$current->save();
return back();
}
/**
* retreive order position to put a new slide to
*
* @return integer
*/
private function getLastOrder()
{
$order = Slide::select('order')->orderBy('order', 'DESC')->first();
if ($order === null) {
return 1;
}
return $order->order + 1;
}
}

View file

@ -0,0 +1,149 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\StaticPages\StoreStaticPageRequest;
use App\Http\Requests\Bo\StaticPages\UpdateStaticPageRequest;
use App\Models\Site\StaticPage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class StaticPageController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $static_pages */
$static_pages = StaticPage::query()->where('type', '=', StaticPageType::crud());
if ($request->search) {
$this->searchQuery(
$static_pages,
$request->search,
null,
'title',
'value'
);
} //end if
$this->sortQuery($static_pages);
$searchFields = trans('Titre, contenu');
$static_pages->orderBy('updated_at', 'DESC');
$static_pages = $static_pages->paginate();
return view('back::pages.static_pages.index', compact('static_pages', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('back::pages.static_pages.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\StaticPages\StoreStaticPageRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreStaticPageRequest $request)
{
return DB::transaction(function () use ($request) {
$static_page = new StaticPage();
// * Only crud pages are allowed to be created.
$static_page->type = StaticPageType::crud();
$static_page->fill($request->validated());
if ($static_page->saveOrFail()) {
return redirect()
->to(
\App\Models\Site\StaticPage::getBoRouteForType($static_page->type, 'edit') ??
route('bo.static_pages.edit', $static_page->id)
)
->with('bo.success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.static_pages'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Site\StaticPage $static_page
* @return \Illuminate\Http\Response
*/
public function edit(StaticPage $static_page)
{
return view('back::pages.static_pages.edit', ['static_pageModel' => $static_page]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\StaticPages\UpdateStaticPageRequest $request
* @param \App\Models\Site\StaticPage $static_page
* @return \Illuminate\Http\Response
*/
public function update(UpdateStaticPageRequest $request, StaticPage $static_page)
{
return DB::transaction(function () use ($request, $static_page) {
try {
$static_page->fill($request->validated());
} catch (\ErrorException $e) {
return back()
->with('bo.danger', trans(
':model n\'a pas pu être enregistré car l\'image n\'a pu être transférée.',
['model' => Str::singular(trans('models.classes.pages'))]
));
}
if ($static_page->saveOrFail()) {
return redirect()
->to(
\App\Models\Site\StaticPage::getBoRouteForType($static_page->type, 'edit') ??
route('bo.static_pages.edit', $static_page->id)
)
->with('bo.success', trans(
':model a été mis à jour',
['model' => trans('Page')]
));
}
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Site\StaticPage $static_page
* @return \Illuminate\Http\Response
*/
public function destroy(StaticPage $static_page)
{
if ($static_page->deleteOrFail()) {
return redirect()
->route('bo.static_pages.index')
->with('bo.warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.static_pages'))]
));
}
return back();
}
}

View file

@ -0,0 +1,154 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Tags\StoreTagRequest;
use App\Http\Requests\Bo\Tags\UpdateTagRequest;
use App\Models\Merchandise\Tag;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Controllers\UpdateModelStatus;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class TagController extends Controller
{
use ChangesModelOrder;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $tags */
$tags = Tag::query();
if ($request->search) {
$this->searchQuery(
$tags,
$request->search,
null,
'label'
);
}
$this->sortQuery($tags);
$searchFields = trans('Label');
$tags = $tags->paginate();
return view('back::pages.tags.index', compact('tags', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('back::pages.tags.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tags\StoreTagRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreTagRequest $request)
{
return DB::transaction(function () use ($request) {
$tag = new Tag();
$tag->fill($request->validated());
if ($tag->saveOrFail()) {
$tag->products()->sync($request->product_list);
return redirect()
->route('bo.tags.edit', $tag)
->with('bo.success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.tags'))]
));
}
return back();
});
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tags\StoreTagRequest $request
* @return \Illuminate\Http\Response
*/
public function jsonStore(StoreTagRequest $request)
{
return DB::transaction(function () use ($request) {
/** @var \App\Models\Merchandise\Tag */
$tag = Tag::where('slug', Str::slug($request->name))->firstOrNew();
$tag->fill($request->validated());
$saved = $tag->saveOrFail();
return \response()->json($saved ? $tag->toArray() : [], $saved ? 200 : 500);
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Merchandise\Tag $tag
* @return \Illuminate\Http\Response
*/
public function edit(Tag $tag)
{
return view('back::pages.tags.edit', ['tagModel' => $tag]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Tags\UpdateTagRequest $request
* @param \App\Models\Merchandise\Tag $tag
* @return \Illuminate\Http\Response
*/
public function update(UpdateTagRequest $request, Tag $tag)
{
return DB::transaction(function () use ($request, $tag) {
$tag->fill($request->validated());
$tag->products()->sync($request->product_list);
if ($tag->saveOrFail()) {
return redirect()
->route('bo.tags.edit', $tag)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.tags'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Merchandise\Tag $tag
* @return \Illuminate\Http\Response
*/
public function destroy(Tag $tag)
{
if ($tag->deleteOrFail()) {
return redirect()
->route('bo.tags.index')
->with('bo.warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.tags'))]
));
}
return back();
}
}

View file

@ -1,174 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class TagsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$tags = Tag::orderBy('order', 'ASC')->get();
return view('bo.tags.index', compact('tags'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.tags.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$label = $request->label;
$slug = Str::slug($label);
$status = $request->status === null ? 0 : 1;
$request->validate([
'label' => 'required|string|max:255',
]);
$tag = new Tag();
$tag->label = $label;
$tag->slug = $slug;
$tag->status = $status;
$tag->order = $this->getLastOrder();
$tag->save();
return redirect()->route('bo.tags.index');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$tag = Tag::findOrFail($id);
return view('bo.tags.edit', compact('tag'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$label = $request->label;
$slug = Str::slug($label);
$status = $request->status === null ? 0 : 1;
$request->validate([
'label' => 'required|string|max:255',
]);
$tag = Tag::findOrFail($id);
$tag->label = $label;
$tag->slug = $slug;
$tag->status = $status;
$tag->save();
return redirect()->route('bo.tags.edit', $tag->id)->with('success', 'Modifications enregistrés !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$tag = Tag::findOrFail($id);
$products = $tag->products()->count();
if ($products !== 0) {
return redirect()->back()
->withErrors("Suppression impossible : \"" . $tag->label . "\" est associé à des produits.");
}
$tag->delete();
return redirect()->route('bo.tags.index');
}
/**
* Change Tag Order
*
* @param integer $id
* @param string $action
* @return \Illuminate\Http\Response
*/
public function changeOrder(int $id, string $action)
{
$current = Tag::findOrFail($id);
if ($action === 'up') {
$tmp = Tag::where('order', '<', $current->order)->orderBy('order', 'DESC')->first();
$newOrder = $tmp->order;
$tmp->order = $current->order;
$current->order = $newOrder;
} else {
$tmp = Tag::where('order', '>', $current->order)->orderBy('order', 'ASC')->first();
$newOrder = $tmp->order;
$tmp->order = $current->order;
$current->order = $newOrder;
}
$tmp->save();
$current->save();
return back();
}
/**
* Toggle tag status
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function toggleStatus(int $id)
{
$tag = Tag::findOrFail($id);
$tag->status = !($tag->status);
$tag->save();
return back();
}
/**
* retreive order position to put a new slide to
*
* @return integer
*/
public static function getLastOrder()
{
$order = Tag::select('order')->orderBy('order', 'DESC')->first();
if ($order === null) {
return 1;
}
return $order->order + 1;
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\TimeSlot\WeekDay;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Timeslots\UpdateTimeslotRequest;
use App\Models\Shop\Shop;
use App\Models\Shop\TimeslotSetting;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class TimeslotController extends Controller
{
/**
* Display timeslot settings form
*
* @param \App\Models\Shop\Shop $shop
* @return \Illuminate\Contracts\View\View
*/
public function settings(Shop $shop)
{
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Shop\TimeslotSetting> */
$timeslotSettings = $shop->timeslotSettings;
return view('back::pages.timeslots.settings', [
'timeslotSettings' => $timeslotSettings,
'shopModel' => $shop
]);
}
/**
* save settings
*
* @param \App\Http\Requests\Bo\Timeslots\UpdateTimeslotRequest $request
* @param \App\Models\Shop\Shop $shop
* @return \Illuminate\Http\RedirectResponse
*/
public function updateSettings(UpdateTimeslotRequest $request, Shop $shop)
{
return DB::transaction(function () use ($request, $shop) {
$validated = $request->validated()['plages'];
foreach (WeekDay::toArray() as $weekDay) {
$setting = TimeslotSetting::where('day', $weekDay)
->whereHas('shop', function (Builder $q) use ($shop) {
$q->where('id', $shop->id);
})->firstOrFail();
$setting->fill($validated[$weekDay->value]);
$setting->saveOrFail();
}
return redirect()->route('bo.timeslots.settings', $shop)
->with('bo.success', 'Modifications enregistrées !');
});
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\XmlIncos\SearchXmlInco;
use App\Http\Resources\XmlIncoCollection;
use App\Models\XmlInco;
use Illuminate\Support\Facades\Storage;
use Kwaadpepper\ImageResizer\ImageResizer;
use Symfony\Component\HttpFoundation\StreamedResponse;
class XmlIncoController extends Controller
{
/**
* Download a file from inco storage.
*
* @param string $inco_picture
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*/
public function getXmlIncoPicture(string $inco_picture): StreamedResponse
{
if (!($picturePath = XmlInco::getXmlIncoPicturePath($inco_picture))) {
abort(404);
}
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = XmlInco::getDisk();
// * Resized inco picture if wanted.
if (request()->get('resize')) {
$resizedPath = ImageResizer::resizeImageOrIgnore(
$disk->path($picturePath),
'boIncoPicture'
);
$picturePath = $resizedPath ? $resizedPath : $picturePath;
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('public');
}
return \response()->streamDownload(function () use ($disk, $picturePath) {
$stream = $disk->readStream($picturePath);
\fpassthru($stream);
\fclose($stream);
}, \pathinfo($inco_picture, \PATHINFO_BASENAME), [
'Content-Type' => $disk->mimeType($picturePath),
'Content-Length' => $disk->size($picturePath),
'Content-Disposition' => 'inline',
]);
}
/**
* Search for xml inco
*
* @param \App\Http\Requests\Bo\XmlIncos\SearchXmlInco $request
* @return \App\Http\Resources\XmlIncoCollection
*/
public function getXmlIncos(SearchXmlInco $request)
{
return new XmlIncoCollection(XmlInco::where('id', 'LIKE', "%$request->term%")
->orWhere('descriptif', 'LIKE', "%$request->term%")
->get());
}
}

View file

@ -1,56 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ContactFormRequest;
use App\Mail\SendContactMail;
use App\Models\Magasin;
use App\Models\Setting;
use App\Models\Content;
use Illuminate\Support\Facades\Mail;
use Illuminate\Http\Request;
class ContactController extends Controller
{
/**
* legal notice page
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function contact(Request $request)
{
// @phpcs:enable
$status = $request->session()->get('status');
// 1er magasin en base pour le moment
$magasin = Magasin::first();
$content = Content::where('key', 'contact')->first();
return view('contact', compact('status', 'magasin', 'content'));
}
/**
* submit contact form
*
* @param ContactFormRequest $request
* @return \Illuminate\Http\Response
*/
public function contactSubmit(ContactFormRequest $request)
{
$data = $request->all();
$toMailSetting = Setting::where('key', 'send_contact_mail_to')->first();
if (empty($toMailSetting->value)) {
$toMail = config('mail.from.address');
} else {
$toMail = $toMailSetting->value;
}
// Envoi du mail d'alerte.
Mail::to($toMail)->send(new SendContactMail($request));
return back()->with('status', true);
}
}

View file

@ -2,14 +2,272 @@
namespace App\Http\Controllers;
use App\Enums\Order\TransactionType;
use App\Models\Payment\Transaction;
use App\Models\Shop\DeliveryMode;
use App\Models\Shop\Order;
use App\Models\Shop\PaymentMode;
use Gloudemans\Shoppingcart\Cart;
use Gloudemans\Shoppingcart\CartItem;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class Controller extends BaseController
{
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
/**
* Cart item options skel
*
* @var string[]
*/
public static $cartItemOptionsSkel = [
'virtual' => false,
'reference' => '',
'slug' => '',
'picture' => '',
'sell_unit' => '',
'weight_with_unit' => '',
// * Prix ttc du produit
'price_ttc' => 0,
// * Prix ttc du produit remisé
'undiscounted_price_ttc' => 0,
'tax_rate' => 0,
'discount_type' => null,
'discount_rate' => 0,
];
/**
* Controller
*
* @return void
*/
public function __construct()
{
$this->middleware(function (Request $request, callable $next) {
$reponse = $next($request);
if (\request()->routeIs('bo.*') and !\request()->ajax()) {
if (!DeliveryMode::getCached()->where('status', true)->count()) {
Session::flash('bo.warning', trans('Aucun mode de livraison n\'est disponible'));
}
if (!PaymentMode::getCached()->where('status', true)->count()) {
Session::flash('bo.warning', trans('Aucun mode de paiement n\'est disponible'));
}
}
return $reponse;
});
}
/**
* Build Search query on specified fields
* splitting search into words or using whole sentence
*
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields
* would be concatenate to make the search.
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function searchQuery(
Builder $query,
string $search,
callable $searchQuery = null,
string ...$fields
): Builder {
if (count($fields)) {
// Search using words.
$words = explode(' ', $search);
$query->where(function ($query) use ($searchQuery, $words, $search, $fields) {
foreach ($words as $word) {
if (!strlen($word)) {
continue;
}
$this->searchOnFields($query, $word, $fields);
} //end foreach
// Search whole using whole sentence.
$this->searchOnFields($query, $search, $fields);
if ($searchQuery) {
$searchQuery($query);
}
});
} //end if
return $query;
}
/**
* Build query search on all fields
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @param array $fields
* @return \Illuminate\Database\Eloquent\Builder
*/
private function searchOnFields(Builder $query, string $search, array $fields): Builder
{
$table = $query->getModel()->getTable();
foreach ($fields as $f) {
$f = str_replace('?', '\?', $f);
if (is_array($f)) {
$query->orWhereRaw(
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($f)->map(function ($q) use ($table) {
return "`$table`.`$q`";
})->implode(', \' \', ')),
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
);
} else {
$query->orWhereRaw(
"UPPER(`$table`.`$f`) LIKE UPPER(?)",
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
);
}
}
return $query;
}
/**
* Sort columns with a query
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param array $ignore
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function sortQuery(Builder $query, array $ignore = []): Builder
{
$rName = request()->route()->getName();
$table = $query->getModel()->getTable();
if (request()->sort_col) {
Session::put("$rName.sort_col", request()->sort_col);
Session::put("$rName.sorted", true);
}
if (request()->sort_way) {
Session::put("$rName.sort_way", request()->sort_way);
Session::put("$rName.sorted", true);
}
if (request()->rst) {
Session::remove("$rName.sorted");
Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way");
}
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
if (
!isset($ignore) or
(isset($ignore) and !in_array(request()->sort_col, $ignore))
) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
}
} elseif (Schema::hasColumn($table, 'order')) {
$query = $query->orderBy('order', 'ASC');
Session::put("$rName.sort_col", 'order');
Session::put("$rName.sort_way", 'asc');
}
return $query;
}
/**
* Filter basket items to keep only the orderable ones
*
* ! Assert Model found, status true and orderable true.
*
* @param \Gloudemans\Shoppingcart\Cart $basket
* @param array<string> $messages Will be filled with messages on warnings.
* @return \Gloudemans\Shoppingcart\Cart
*/
protected function filterBasketKeepOnlyOrderableItems(Cart $basket, array &$messages = []): Cart
{
\collect($basket->content())->each(function (CartItem $item) use (&$basket, &$messages) {
/** @var \App\Models\Merchandise\Product|null */
$product = $item->model;
if (!$product or (!$product and (!$product->status or !$product->orderable))) {
$basket->remove($item->rowId);
$messages[] = \trans(
'Le produit :name a été retiré du panier car il n\'est plus disponible',
[
'name' => $item->name
]
);
}
});
return $basket;
}
/**
* Generate and download transaction invoice
*
* ! Appliable for both Fo and Bo controllers
*
* @param \App\Models\Shop\Order $order
* @param \App\Models\Payment\Transaction $transaction
* @return \Symfony\Component\HttpFoundation\Response
*/
public function downloadTransactionInvoice(Order $order, Transaction $transaction): Response
{
$this->authorize('download', $transaction);
$title = trans('Commande du :date - :orderRef - :transRef', [
'date' => $transaction->created_at->isoFormat('DD\/MM\/YYYY'),
'orderRef' => $order->reference,
'transRef' => $transaction->reference,
]);
$sluggedTitle = Str::slug(\implode(' ', [
config('app.name'),
$transaction->type->equals(TransactionType::debit()) ? trans('Facture') : trans('Avoir'),
$transaction->created_at->isoFormat('YYYY-MM-DD'),
]));
return \response()->streamDownload(function () use ($transaction, $title) {
echo $transaction->generateInvoice($title)->output();
}, "{$sluggedTitle}.pdf", [
'Cache-Control' => 'public, must-revalidate, max-age=0',
'Content-Description' => 'File Transfer',
'Content-Type' => 'application/pdf',
'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT',
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
// For glory.
'X-Generator' => 'mPDF'
]);
}
/**
* Generate and download transaction invoice
*
* ! Appliable for both Fo and Bo controllers
*
* @param \App\Models\Shop\Order $order
* @return \Symfony\Component\HttpFoundation\Response
*/
public function downloadOrderInvoice(Order $order): Response
{
$this->authorize('download', $order);
$title = trans('Commande du :date - :orderRef - :transRef', [
'date' => $order->created_at->isoFormat('DD\/MM\/YYYY'),
'orderRef' => $order->reference,
'transRef' => trans('Toutes transactions'),
]);
$sluggedTitle = Str::slug(\implode(' ', [
config('app.name'),
trans('Facture'),
$order->created_at->isoFormat('YYYY-MM-DD'),
]));
return \response()->streamDownload(function () use ($order, $title) {
echo $order->generateInvoice($title)->output();
}, "{$sluggedTitle}.pdf", [
'Cache-Control' => 'public, must-revalidate, max-age=0',
'Content-Description' => 'File Transfer',
'Content-Type' => 'application/pdf',
'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT',
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
// For glory.
'X-Generator' => 'mPDF'
]);
}
}

View file

@ -1,606 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CustomerRegisterRequest;
use App\Http\Requests\LoginRequest;
use App\Http\Requests\UpdateInfosRequest;
use App\Mail\PasswordResetMail;
use App\Mail\RegisterConfirmMail;
use App\Models\Customers;
use App\Models\Magasin;
use App\Models\Order;
use App\Models\PasswordReset;
use App\Models\Salarie;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Enums\Country;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Exceptions\ValidationException;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
class CustomerController extends Controller
{
/**
* Affiche la page d'inscription
*
* @return \Illuminate\Http\Response
*/
public function registerForm()
{
if (Auth::guard('customer')->check()) {
return redirect()->route('home');
}
return view('auth.register');
}
/**
* Enregistre le customer dans la base de données
* et CRM
*
* @param CustomerRegisterRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function register(CustomerRegisterRequest $request)
{
try {
return DB::transaction(function () use ($request) {
$crmCustomer = new CrmCustomer(\array_intersect_key(
$request->validated(),
[
'firstname' => '',
'lastname' => '',
'birth_date' => '',
'address' => '',
'postcode' => '',
'country' => '',
'city' => '',
'phone' => '',
'email' => '',
'password' => '',
'opt_in' => '',
]
));
$crmCustomer->city = $request->town;
$crmCustomer->postcode = $request->postal_code;
$crmCustomer->opt_in = $request->optin ? true : false;
// * Country Is forced to france
$crmCustomer->country = Country::france();
$crmCustomer->save();
// * Enrregistrement du Hashcode
/** @var \App\Models\Customers $customer */
$customer = new Customers($request->all());
$customer->hashcode = $crmCustomer->hashcode;
$customer->saveOrFail();
// * liaison éventuelle avec la table salaries
if ($request->matricule && $request->code) {
/** @var \App\Models\Salarie $salarie */
$salarie = Salarie::where('matricule', '=', $request->matricule)
->where('code', '=', $request->code)
->firstOrFail();
$salarie->customer()->associate($customer);
$salarie->saveOrFail();
}
Mail::to($request->email)->send(new RegisterConfirmMail($customer, $request->firstname));
return view('auth.register-success', ['mail' => $request->email]);
});
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::customerExists())) {
return back()->withInput()->withErrors('Un compte est déjà associé à cette adresse email.');
}
if ($e->getApiError()->equals(ApiError::validationFailed())) {
return back()->withInput()->withErrors($e->getMessage());
}
report($e);
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
} catch (ModelNotFoundException $e) {
return back()->withErrors("Salarié $request->matricule, $request->code introuvable.");
} catch (ValidationException $e) {
return back()->withErrors($e->getMessage());
} catch (\Exception | \TypeError | CrmException $e) {
report($e);
// * Une erreur critique est survenue avec la CRM
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
} //end try
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
}
/**
* Retourne la page de connexion
*
* @param Request $request
* @return \Illuminate\Http\Response
*/
public function authenticateForm(Request $request)
{
if (Auth::guard('customer')->check()) {
return redirect()->route('account.dashboard');
}
if ($request->get('url')) {
Session::put('url.intended', $request->get('url'));
Session::save();
}
return view('auth.login');
}
/**
* Gère l'authentification des customers
*
* @param LoginRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function authenticate(LoginRequest $request)
{
/** @var Customers|null */
$customer = Customers::where('email', $request->email)->first();
try {
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
// * Ne devrait pas survenir, $crm->login throws ApiError::authFailed()
if (!$crm->login($request->email, $request->password)) {
// * mauvais identifiants
return back()->withErrors('Email ou mot de passe incorrect.');
}
// Dans tout les cas : récupérer le crm user.
$crmCustomer = $crm->getCustomer($request->email);
// Si pas de compte local mais existe en CRM.
if (!$customer) {
$customer = $this->createLocalCustomer($crmCustomer);
}
return $this->processLogin($customer, $request->remember);
} catch (ApiErrorException $e) {
return $this->handleLoginApiErrorException($e, $request);
} catch (CrmException | \Exception $e) {
report($e);
// Une erreur critique est survenue avec la CRM.
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
} //end try
}
/**
* Active le compte d'un customer
*
* @param mixed $hashcode
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function activate($hashcode)
{
$hashcode = filter_var($hashcode, FILTER_SANITIZE_STRING);
if (!($customer = Customers::select('hashcode')->where('hashcode', $hashcode)->first())) {
return view('auth.activate')->with('message', 'Ce lien d\'activation n\'est pas valide.');
}
try {
/** @var \LDC\LDCCRM\Models\Customer $crmCustomer */
$crmCustomer = $customer->apiData;
if ($crmCustomer->activate()) {
return view('auth.activate')
->with('message', 'Votre compte a bien été activé, vous pouvez désormais vous connecter.');
}
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::customerAlreadyActive())) {
// phpcs:disable Generic.Files.LineLength.TooLong
return view('auth.activate')
->with('message', 'Votre compte est déjà activé, vous pouvez dès à présent vous connecter avec celui-ci.');
}
report($e);
// Une erreur critique est survenue avec la CRM.
return view('auth.activate')
->with('message', 'Une erreur est survenue, merci de réessayer ultérieurement.');
} catch (CrmException $e) {
report($e);
// Une erreur critique est survenue avec la CRM.
return view('auth.activate')
->with('message', 'Une erreur est survenue, merci de réessayer ultérieurement.');
} //end try
return view('auth.activate')->with('message', 'Ce lien d\'activation n\'est pas valide.');
}
/**
* deconnexion customer
*
* @return \Illuminate\Http\RedirectResponse
*/
public function logout()
{
if (Auth::guard('customer')->check()) {
Auth::guard('customer')->logout();
return redirect()->route('home');
}
}
/**
* page d'accès à la procédure mdp oublié
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function forgotPasswordForm(Request $request)
{
// @phpcs:enable
return view('auth.passwords.forgot');
}
/**
* initialisation de la procédure de mdp oublié
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function passwordReset(Request $request)
{
$request->validate([
'email' => 'required|email:rfc|max:255',
]);
$email = filter_var($request->email, FILTER_SANITIZE_EMAIL);
$customerExist = Customers::where('email', $email)->count() === 0 ? false : true;
if ($customerExist) {
// Suppression des anciens tokens.
PasswordReset::where('email', $email)->delete();
$token = Str::random(80);
$passwordReset = new PasswordReset();
$passwordReset->email = $email;
$passwordReset->token = $token;
$passwordReset->created_at = now();
$passwordReset->save();
Mail::to($email)->send(new PasswordResetMail($token));
return back()->with(
'success',
__("La procédure pour changer votre mot de passe a été envoyé sur votre email.")
);
} else {
return back()->withErrors(__("Cette adresse email n'est pas enregistrée dans nos services."));
}
}
/**
* Print the view used to change the password of a user when he required it
* @param string $token Token used to identificate the user to change the password.
* @return \Illuminate\Contracts\View\View The view to print
*/
public function viewChangePassword(string $token)
{
$pwdReset = PasswordReset::where('token', $token)->first();
if (!$pwdReset) {
abort(419);
}
$customer = Customers::where('email', $pwdReset->email)->first();
if (!$customer) {
abort(404);
}
return view('auth.passwords.reset', compact('customer'))->with('token', $token);
}
/**
* changement de mot de passe (depuis la procédure de mdp oublié)
*
* @param Request $request
* @param mixed $token
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function changePassword(Request $request, $token)
{
$request->validate([
'password' => 'required|confirmed|min:8|max:255'
]);
/** @var \App\Models\PasswordReset $passwordReset */
$passwordReset = PasswordReset::where('token', $token)->first();
/** @var \App\Models\Customers $customer */
$customer = Customers::where('email', optional($passwordReset)->email)->firstOrFail();
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
try {
if ($crm->login($customer->email, $request->password)) {
return back()->withErrors('Vous ne pouvez pas utiliser un de vos anciens mot de passe.');
}
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
} catch (ApiErrorException $e) {
// * continue
}
try {
if (!($crmCustomer = $crm->getCustomer($customer->hashcode))) {
return back()
->withErrors('Changement impossible, compte inexistant.');
}
$crmCustomer->password = $request->password;
$crmCustomer->save();
} catch (\TypeError | CrmException | ApiErrorException $e) {
report($e);
// Une erreur critique est survenue avec la CRM.
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
} catch (ValidationException $e) {
return back()->withErrors(sprintf('Nouveau mot de passe : %s', $e->getMessage()));
}
$passwordReset->deleteOrFail();
return view('auth.passwords.reset-ok', compact('customer'));
}
/**
* profil client (mon compte)
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function dashboard(Request $request)
{
// @phpcs:enable
// Show Profile.
/** @var \App\Models\Customers|null $customer */
$customer = Auth::guard('customer')->user();
if (!$customer) {
abort(404);
}
/** @var \LDC\LDCCRM\Models\Customer $crmCustomer */
$crmCustomer = $customer->apiData;
/** @var object $crmInfos */
$crmInfos = \json_decode(\json_encode($crmCustomer));
$birthDate = explode('-', $crmCustomer->birth_date);
return view('customer.dashboard', compact('customer', 'crmInfos', 'birthDate'));
}
/**
* Send a request to the CRM to change the given customer's informations
* @param UpdateInfosRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function updateInfos(UpdateInfosRequest $request)
{
if (($result = $this->updateUserInfo($request)) === true) {
return back()->with('success', trans(
'Vos informations ont bien été enregistrées.'
));
}
return back()->withInput()->withErrors(
$result !== false ? $result : trans(
'Une erreur est survenue, merci de réessayer ultérieurement.'
)
);
}
/**
* changement de mot de passe depuis le profil
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function changePasswordWhenAuthenticated(Request $request)
{
$request->validate([
'password' => 'required|min:8|confirmed|max:255'
]);
$crm = app('crmApi');
$customer = $crm->changePwdCustomer(Auth::guard('customer')->user()->crm_id, $request->password);
if ($customer->status === "OK") {
return back()->with('success', __("Modifications enregistrés !"));
} else {
return back()->withErrors(__("Une erreur est survenue, merci de réessayer ultérieurement."));
}
}
/**
* affichage des commandes du customer connecté
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function myOrders(Request $request)
{
// @phpcs:enable
$orders = Order::where('customer_id', Auth::guard('customer')->user()->id)->orderBy('date', 'DESC')->get();
return view('customer.my-orders', compact('orders'));
}
/**
* Show detail commande
*
* @param string $reference
* @return \Illuminate\Contracts\View\View
*/
public function orderDetail(string $reference)
{
$order = Order::where('reference', '=', $reference)
->where('customer_id', Auth::guard('customer')->user()->id)
->firstOrFail();
if ($order->status_id == 1) {
$magasin = null;
} else {
$magasin = Magasin::find($order->timeslot->magasin->id);
}
return view('customer.order-detail', compact('order', 'magasin'));
}
/**
* delete customers
*
* @param integer $id
* @return void
*/
public function delete(int $id)
{
dump('deleting' . $id);
die('Not implemented yet');
}
/**
* Create a local customer
*
* @param CrmCustomer $crmCustomer
* @return \app\Models\Customers
* @throws \Throwable If saving to DB failed.
*/
private function createLocalCustomer(CrmCustomer $crmCustomer): Customers
{
/** @var \App\Models\Customers $customer */
$customer = Customers::firstOrNew([
'email' => $crmCustomer->email
]);
$customer->hashcode = $crmCustomer->hashcode;
$customer->saveOrFail();
return $customer;
}
/**
* Handle Api error exception
*
* @param ApiErrorException $e
* @param LoginRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
private function handleLoginApiErrorException(ApiErrorException $e, LoginRequest $request)
{
/** @var Customers|null */
$customer = Customers::where('email', $request->email)->first();
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
switch ($e->getApiError()) {
case ApiError::authFailed():
// * mauvais identifiants
return back()->withErrors('Email ou mot de passe incorrect.');
case ApiError::customerNotActivated():
// * le compte n'est pas activé
if (!$customer) {
try {
if (!($crmCustomer = $crm->getCustomer($request->email))) {
Log::critical(sprintf('Customer %s coulnd not be retrieved from crm', $request->email));
return back()->withErrors(
trans('Une erreur est survenue, merci de réessayer ultérieurement.')
);
}
$customer = $this->createLocalCustomer($crmCustomer);
} catch (CrmException | ApiErrorException $e) {
report($e);
// Une erreur critique est survenue avec la CRM.
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
}
}
Mail::to($request->email)->send(new RegisterConfirmMail($customer, null));
return back()
->withErrors(
trans('Votre compte n\'est pas activé, vérifiez dans vos emails pour y trouver
le mail de confirmation. Un lien d\'activation vous a été renvoyé.')
);
default:
report($e);
// * une erreur non gérée
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
} //end switch
}
/**
* Login a customer
*
* @param Customers $customer
* @param boolean $remember
* @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
*/
private function processLogin(Customers $customer, bool $remember)
{
/** @var \Illuminate\Auth\SessionGuard */
$guard = auth('customer');
$guard->login($customer, $remember);
$url = Session::pull('url.intended');
Session::forget('url.intended');
Session::save();
return !empty($url) ? redirect()->to($url) : redirect()->route('account.dashboard');
}
/**
* Updates user info
*
* @param UpdateInfosRequest $request
* @return boolean|string return strictly true if success,
* false il failed, could be a string with message error if failed
*/
private function updateUserInfo(UpdateInfosRequest $request)
{
/** @var \App\Models\Customers */
$customer = auth('customer')->user();
if (!$customer) {
return \redirect()->back();
}
/** @var \LDC\LDCCRM\Models\Customer */
$crmCustomer = $customer->apiData;
$data = [
'firstname' => $request->firstname,
'lastname' => $request->lastname,
'birth_date' => $request->year . '-' . $request->month . '-' . $request->day,
'address' => $request->address,
'postcode' => $request->postcode,
'city' => $request->city,
'phone' => \strval($request->phone),
'opt_in' => \boolval($request->optin),
];
// * remove phone if empty
if (isset($data['phone']) and !strlen($data['phone'])) {
unset($data['phone']);
}
try {
$crmCustomer->fill($data);
$crmCustomer->save();
} catch (ValidationException $e) {
return $e->getMessage();
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::validationFailed())) {
return $e->getMessage();
}
report($e);
return false;
} catch (CrmException | \TypeError $e) {
report($e);
return false;
}
return true;
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
use Illuminate\Support\Facades\Auth;
class ConfirmPasswordController extends Controller
{
use ConfirmsPasswords;
/**
* Display the password confirmation view.
*
* @return \Illuminate\View\View
*/
public function showConfirmForm()
{
if (Auth::guard('frontend')->check()) {
return \response()->redirectToRoute('home');
}
return view('theme::auth.passwords.confirm');
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToRoute('account.dashboard')->getTargetUrl();
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
class ForgotPasswordController extends Controller
{
use SendsPasswordResetEmails;
/**
* Message to user
*
* @var string
*/
private $message =
'Si votre compte existe vous devriez recevoir un message avec un lien pour réinitialiser votre mot de passe';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:frontend');
}
/**
* Display the form to request a password reset link.
*
* @return \Illuminate\View\View
*/
public function showLinkRequestForm()
{
return view('theme::auth.passwords.email');
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker('customers');
}
/**
* Get the response for a successful password reset link.
* ! Dans les deux cas on ne divulgue pas si un user existe.
*
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendResetLinkResponse()
{
return back()->with('info', trans($this->message));
}
/**
* Get the response for a failed password reset link.
* ! Dans les deux cas on ne divulgue pas si un user existe.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendResetLinkFailedResponse(Request $request, string $response)
{
// ! Ne pas notifier que l'utilisateur n'existe pas.
if ($response === 'passwords.user') {
return back()->with('info', trans($this->message));
}
// * Renvoyer les autres message d'erreur style throttle.
return back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
class LoginController extends Controller
{
use AuthenticatesUsers;
use ThrottlesLogins;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:frontend')->except('logout');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('frontend');
}
/**
* Show the application's login form.
*
* @return \Illuminate\View\View
*/
public function showLoginForm()
{
return view('theme::auth.login');
}
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Database\Eloquent\Model $user
* @return mixed
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing, Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
protected function authenticated(Request $request, Model $user)
{
$user->last_connection = \now();
$user->saveOrFail();
Session::flash('info', trans('Vous êtes à présent connecté'));
}
/**
* The user has logged out of the application.
*
* @param \Illuminate\Http\Request $request
* @return mixed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
protected function loggedOut(Request $request)
{
Session::flash('info', trans('Vous êtes à présent déconnecté'));
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToIntended()->getTargetUrl();
}
}

View file

@ -0,0 +1,184 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Customers\CreateAccountRequest;
use App\Models\Customer\Customer;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Enums\Country;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\ValidationException as ExceptionsValidationException;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
class RegisterController extends Controller
{
use RegistersUsers;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:frontend');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('frontend');
}
/**
* Show the application registration form.
*
* @return \Illuminate\View\View
*/
public function showRegistrationForm()
{
return view('theme::auth.register');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
* @throws \Illuminate\Validation\ValidationException If data is incorrect.
*/
protected function validator(array $data)
{
$data['ok_cgv'] = \request()->boolean('ok_cgv');
$data['phone'] = $data['phone'] ?? null;
$data['opt_in'] = ($data['opt_in'] ?? false) ? true : false;
$request = new CreateAccountRequest();
$validateData = Validator::make(
$data,
$request->rules(),
$request->messages(),
$request->attributes()
);
if ($validateData) {
return $validateData;
} else {
try {
(new CrmCustomer())->fill($this->getOnlyCrmData($data));
} catch (ExceptionsValidationException $e) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
}
}
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\Models\Customer
* @throws \Illuminate\Validation\ValidationException If there are validation errors.
* @throws \RuntimeException On unhandled api Error.
*/
protected function create(array $data)
{
$data['country'] = Country::france();
$data['ok_cgv'] = \request()->boolean('ok_cgv');
$data['phone'] = $data['phone'] ?? null;
$data['opt_in'] = ($data['opt_in'] ?? false) ? true : false;
try {
return DB::transaction(function () use ($data) {
$crmCustomer = new CrmCustomer($this->getOnlyCrmData($data));
// * Country Is forced to france
$crmCustomer->country = Country::france();
$crmCustomer->save();
// * Enregistrement du Hashcode
/** @var \App\Models\Customer\Customer $customer */
$customer = new Customer($data);
$customer->hashcode = $crmCustomer->hashcode;
// * Customer will be logged in.
$customer->last_connection = \now();
$customer->saveOrFail();
if ($customer->saveOrFail()) {
// phpcs:ignore Generic.Files.LineLength.TooLong
Session::flash('success', trans('Votre compte à bien été crée, vous devez valider votre adresse email, un email de confirmation vous à été envoyé.'));
}
return $customer;
});
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::customerExists())) {
throw ValidationException::withMessages([
'error' => 'Un compte est déjà associé à cette adresse email.'
]);
}
if ($e->getApiError()->equals(ApiError::validationFailed())) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
}
report($e);
Log::error(__CLASS__ . '::create reached end of possibilities.');
throw new \RuntimeException(__CLASS__ . '::create reached end of possibilities.');
} catch (ExceptionsValidationException $e) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
} //end try
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToRoute('account.dashboard')->getTargetUrl();
}
/**
* Get only data used in crm
*
* @param array $data
* @return array
*/
private function getOnlyCrmData(array $data): array
{
return \array_intersect_key(
$data,
[
'firstname' => '',
'lastname' => '',
'birth_date' => '',
'address' => '',
'postcode' => '',
'country' => '',
'city' => '',
'phone' => '',
'email' => '',
'password' => '',
'opt_in' => '',
]
);
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Exceptions\ValidationException as ExceptionsValidationException;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:frontend');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('frontend');
}
/**
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function showResetForm(Request $request)
{
$token = $request->route()->parameter('token');
return view('theme::auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
/**
* Reset the given user's password.
*
* @param mixed $customer
* @param mixed $password
* @return void
* @throws \Illuminate\Validation\ValidationException If something goes wrong.
*/
protected function resetPassword($customer, $password)
{
/** @var \App\Models\Customer\Customer $user */
/** @var string $password */
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
try {
if (!($crmCustomer = $crm->getCustomer($customer->hashcode))) {
throw ValidationException::withMessages([
'error' => 'Changement impossible, compte inexistant.'
]);
}
$crmCustomer->password = $password;
$crmCustomer->save();
} catch (\TypeError | CrmException | ApiErrorException $e) {
report($e);
throw ValidationException::withMessages([
'error' => 'Une erreur est survenue, merci de réessayer ultérieurement.'
]);
} catch (ExceptionsValidationException $e) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
}
$customer->setRememberToken(Str::random(60));
$customer->save();
event(new PasswordReset($customer));
$this->guard()->login($customer);
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToRoute('account.dashboard')->getTargetUrl();
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker('customers');
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\VerifiesEmails;
use Illuminate\Http\Request;
class VerificationController extends Controller
{
use VerifiesEmails;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth:frontend');
$this->middleware('signed:relative')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
/**
* Show the email verification notice.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
*/
public function show(Request $request)
{
return $request->user()->hasVerifiedEmail()
? redirect($this->redirectPath())
: view('theme::auth.verify');
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToRoute('account.dashboard')->getTargetUrl();
}
}

View file

@ -0,0 +1,165 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\Product\WeightUnit;
use App\Http\Controllers\Controller;
use App\Lib\Helpers\ToolboxHelper;
use App\Models\Merchandise\Product;
use Gloudemans\Shoppingcart\CartItem;
use Illuminate\Validation\ValidationException;
class BasketController extends Controller
{
/**
* Get Basket
*
* @return \Illuminate\Http\JsonResponse
*/
public function getBasket()
{
$this->authorize('useBasketSystem', Product::class);
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
return \response()->json(ToolboxHelper::cartToList($basket));
}
/**
* Ajout d'un produit au panier
*
* ! Assert Model found, status true and orderable true.
*
* @param \App\Models\Merchandise\Product $product
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException Si le produit n'est pas disponible (orderable).
*/
public function addToBasket(Product $product)
{
$this->authorize('useBasketSystem', Product::class);
$this->authorize('addToCart', $product);
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$basket = $this->filterBasketKeepOnlyOrderableItems($basket);
if (!$product->status or !$product->orderable) {
throw ValidationException::withMessages([
'error' => \trans('Ce produit n\'est plus disponible')
]);
}
$cartItemOptions = \array_merge(static::$cartItemOptionsSkel, [
'reference' => $product->reference,
'slug' => $product->slug,
'picture' => $product->picture,
'sell_unit' => $product->sell_unit,
'weight_with_unit' => WeightUnit::getDisplayableWeight($product->weight),
// * Prix ttc du produit
'price_ttc' => $product->price_ttc,
// * Prix ttc du produit remisé
'undiscounted_price_ttc' => $product->undiscounted_price_ttc,
'tax_rate' => $product->tax_rate->rate,
'discount_type' => \optional(\optional($product->discount)->type)->value,
'discount_rate' => $product->discount_rate,
]);
$cartItem = CartItem::fromBuyable($product, $cartItemOptions);
$cartItem->setQuantity(1);
$cartItem->associate($product);
// * Validate Cart Item.
$jsonCartObject = \json_decode($cartItem->toJson());
if (!ToolboxHelper::basketObjectIsValid($jsonCartObject)) {
throw ValidationException::withMessages([
'error' => \trans('Cart item is not valid')
]);
}
$basket->addCartItem($cartItem);
// ! https://github.com/bumbummen99/LaravelShoppingcart/pull/181
// * Note Lib is is actually refactoring for laravel 9 php8 only
// * https://github.com/bumbummen99/LaravelShoppingcart/pull/145
// ! These lines are required to prevent amount bug (after adding to basket).
// ! This because of the cart calculator I guess.
$cartItem->setTaxRate($product->tax_rate->rate / 100);
$cartItem->setDiscountRate($product->discount_rate);
return \response()->json(ToolboxHelper::cartToList($basket));
}
/**
* Retrait d'un produit du panier (1 quantité d'1 type)
*
* @param \App\Models\Merchandise\Product $product
* @return \Illuminate\Http\JsonResponse
*/
public function removeFromBasket(Product $product)
{
$this->authorize('useBasketSystem', Product::class);
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
/** @var \Gloudemans\Shoppingcart\CartItem|null */
$basketItem = $basket->content()->where('id', $product->id)->first();
/** @var string|null */
$rowId = \optional($basketItem)->rowId;
/** @var integer|null */
$qty = \optional($basketItem)->qty;
if ($rowId and !\is_null($qty)) {
$basket->update($rowId, $qty - 1);
}
$basket = $this->filterBasketKeepOnlyOrderableItems($basket);
return \response()->json(ToolboxHelper::cartToList($basket));
}
/**
* Retrait d'un type produit du panier (toute les quantités d'1 type)
*
* @param \App\Models\Merchandise\Product $product
* @return \Illuminate\Http\JsonResponse
*/
public function removeItemTypeFromBasket(Product $product)
{
$this->authorize('useBasketSystem', Product::class);
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
/** @var \Gloudemans\Shoppingcart\CartItem|null */
$basketItem = $basket->content()->where('id', $product->id)->first();
/** @var string|null */
$rowId = \optional($basketItem)->rowId;
if ($rowId) {
$basket->remove($rowId);
}
$basket = $this->filterBasketKeepOnlyOrderableItems($basket);
return \response()->json(ToolboxHelper::cartToList($basket));
}
/**
* Retrait d'un type produit du panier (toute les quantités d'1 type)
*
* @return \Illuminate\Http\JsonResponse
*/
public function emptyBasket()
{
$this->authorize('useBasketSystem', Product::class);
OrderController::destroyCurrentBasket();
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
return \response()->json(ToolboxHelper::cartToList($basket));
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\ContactFormRequest;
use App\Models\Customer\Contact;
use App\Models\Shop\Shop;
use App\Models\Site\StaticPage;
use Illuminate\Support\Facades\DB;
class ContactController extends Controller
{
/**
* Display contact from
*
* @return \Illuminate\Contracts\View\View
*/
public function contact()
{
$content = StaticPage::getPageOfType(StaticPageType::contact());
$firstShop = Shop::firstOrFail();
return view('theme::pages.contact', compact('content', 'firstShop'));
}
/**
* Submit contact form
*
* @param \App\Http\Requests\Fo\ContactFormRequest $request
* @return \Illuminate\Http\Response
*/
public function contactSubmit(ContactFormRequest $request)
{
return DB::transaction(function () use ($request) {
$contact = new Contact();
$contact->fill($request->validated());
if ($contact->saveOrFail()) {
return redirect()->back()
->with('success', trans('Votre message à été envoyé'));
}
return redirect()->back()
->with('error', trans('Votre message n\'a pas pu être envoyé'));
});
}
}

View file

@ -0,0 +1,267 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Ajax\SubscribeNewsletterRequest;
use App\Http\Requests\Fo\Customers\ConfirmDeleteRequest;
use App\Http\Requests\Fo\Customers\UpdateEmailRequest;
use App\Http\Requests\Fo\Customers\UpdatePasswordRequest;
use App\Http\Requests\Fo\Customers\UpdateInfosRequest;
use App\Models\Customer\Customer;
use App\Models\Shop\Order;
use App\Notifications\Customer\UpdateEmail as NotificationUpdateEmail;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\ValidationException;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Exceptions\ValidationException as CrmValidationException;
use LDC\LDCCRM\Models\SingleOptin;
class CustomerController extends Controller
{
// * --- Backoffice ---
/**
* profil client (mon compte)
*
* @return \Illuminate\Contracts\View\View
*/
public function dashboard()
{
// Show Profile.
/** @var \App\Models\Customer|null $customer */
$customer = Auth::guard('frontend')->user();
if (!$customer) {
abort(404);
}
/** @var \LDC\LDCCRM\Models\Customer $crmCustomer */
$crmCustomer = $customer->apiData;
/** @var object $crmInfos */
$crmInfos = \json_decode(\json_encode($crmCustomer));
return view('theme::pages.customer.dashboard', compact('customer', 'crmInfos'));
}
/**
* Show update customer informations form
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function email()
{
return view('theme::auth.change-email');
}
/**
* Uupdate customer email
*
* @param \App\Http\Requests\Fo\Customers\UpdateEmailRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function emailSubmit(UpdateEmailRequest $request)
{
/** @var \App\Models\Customer\Customer */
$customer = \auth('frontend')->user();
$customer->notify(new NotificationUpdateEmail());
return $this->updateCrmCustomerInfo($customer, ['email' => $request->get('email')]) ?
back()->with('success', trans('Vos informations ont bien été enregistrées.')) :
back()->withInput()->withErrors(trans('Une erreur est survenue, merci de réessayer ultérieurement.'));
}
/**
* Show update customer password form
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function password()
{
return view('theme::auth.passwords.change');
}
/**
* Update customer password
*
* @param UpdatePasswordRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function passwordSubmit(UpdatePasswordRequest $request)
{
/** @var \App\Models\Customer\Customer */
$customer = \auth('frontend')->user();
if ($this->updateCrmCustomerInfo($customer, ['password' => $request->get('password')])) {
event(new PasswordReset($customer));
return back()->with('success', trans('Vos informations ont bien été enregistrées.'));
}
return back()->withInput()->withErrors(trans('Une erreur est survenue, merci de réessayer ultérieurement.'));
}
/**
* Send a request to the CRM to change the given customer's informations
* @param \App\Http\Requests\Fo\UpdateInfosRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function updateInfos(UpdateInfosRequest $request)
{
/** @var \App\Models\Customer\Customer */
$customer = auth('frontend')->user();
return $this->updateCrmCustomerInfo($customer, $request->validated()) ?
back()->with('success', trans('Vos informations ont bien été enregistrées.')) :
back()->withInput()->withErrors(trans('Une erreur est survenue, merci de réessayer ultérieurement.'));
}
/**
* Show customer delete account page
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function destroy()
{
return view('theme::pages.customer.destroy');
}
/**
* Show customer delete account page
*
* @param \App\Http\Requests\Fo\Customers\ConfirmDeleteRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function destroySubmit(ConfirmDeleteRequest $request)
{
if ($request->confirm) {
try {
return DB::transaction(function () {
/** @var \App\Models\Customer\Customer */
$customer = auth('frontend')->user();
$customer->deleteOrFail();
// * Force logout
auth('frontend')->logout();
return response()
->redirectToRoute('home')
->with('info', trans(
'Votre compte à bien été marqué pour suppression,
vous pourrez le réactiver en vous reconnectant à celui-ci avant
qu\'il ne soit définitivement supprimé au bout d\'une semaine.'
));
});
} catch (\Exception $e) {
report($e);
Session::flash('error', trans('Une erreur est survenue veuillez réssayer plus tard.'));
} //end try
} //end if
return back();
}
/**
* Updates user info
*
* @param \App\Models\Customer\Customer $customer
* @param array $data
* @return boolean
* @throws \Illuminate\Validation\ValidationException If something failed.
*/
private function updateCrmCustomerInfo(Customer $customer, array $data)
{
return DB::transaction(function () use ($customer, $data) {
try {
/** @var \LDC\LDCCRM\Models\Customer */
$crmCustomer = $customer->apiData;
if (\array_key_exists('email', $data)) {
$customer->email = $data['email'];
$customer->saveOrFail();
}
$crmCustomer->fill($data);
$saved = $crmCustomer->save();
// * Sync with stripe if already known by stripe.
if ($customer->stripe_id) {
$customer->syncStripeCustomerDetails();
}
return $saved;
} catch (CrmValidationException $e) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::validationFailed())) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
}
report($e);
return false;
} //end try
});
}
/**
* Affichage des commandes du customer connecté
*
* @return \Illuminate\Contracts\View\View
*/
public function myOrders()
{
/** @var \App\Models\Customer\Customer */
$customer = auth('frontend')->user();
$orders = $customer->orders()->orderBy('created_at', 'DESC')->paginate();
return view('theme::pages.customer.orders', compact('orders'));
}
/**
* Show detail commande
*
* @param \App\Models\Shop\Order $order
* @return \Illuminate\Contracts\View\View
*/
public function orderDetail(Order $order)
{
$order->load([
'timeslot.shop',
'transactions.order.customer'
]);
return view('theme::pages.customer.orders.show', compact('order'));
}
// * --- END Backoffice ---
/**
* Call the CRM to add a single optin
*
* @param \App\Http\Requests\Fo\Ajax\SubscribeNewsletterRequest $request
* @return \Illuminate\Http\JsonResponse
*
*/
public function addOptIn(SubscribeNewsletterRequest $request)
{
try {
SingleOptin::setStatus($request->email, true);
return \response()->json();
} catch (ApiErrorException $e) {
// * Il y a deja un customerInfo avec optin a true pour ce customer
if ($e->getApiError()->equals(ApiError::singleOptinCustomerExists())) {
return \response()->json([
'errors' => [
'email' => [\trans('Vous êtes déjà inscrit avec cette adresse email')]
]
], 422);
}
report($e);
return \response()->json([], 500);
} catch (CrmException $e) {
report($e);
return \response()->json([], 500);
}
}
}

View file

@ -0,0 +1,926 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\Order\DeliveryModeEnum;
use App\Enums\Order\DeliveryStatus;
use App\Enums\Order\OrderProcess;
use App\Enums\Order\PaymentModeEnum;
use App\Enums\Order\PaymentStatus;
use App\Enums\Order\TransactionStatus;
use App\Enums\Order\TransactionType;
use App\Enums\Order\ValidationStatus;
use App\Events\OrderIsCreated;
use App\Events\OrderIsPaid;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Orders\CheckoutRequest;
use App\Http\Requests\Fo\Orders\SetOrderTimeSlotRequest;
use App\Http\Requests\Fo\Orders\ShippingFormRequest;
use App\Http\Requests\Fo\Orders\ShippingModeFormRequest;
use App\Http\Resources\ShopResource;
use App\Lib\Helpers\ToolboxHelper;
use App\Models\Customer\Customer;
use App\Models\Payment\Transaction;
use App\Models\Shop\DeliveryMode;
use App\Models\Shop\Order;
use App\Models\Shop\Shop;
use App\Models\Shop\Timeslot;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use LDC\LDCCRM\Enums\Country;
use Ramsey\Uuid\Uuid;
use Symfony\Component\HttpFoundation\Response;
class OrderController extends Controller
{
/**
* OrderController
*
* @return void
*/
public function __construct()
{
$this->middleware(function (Request $request, callable $next) {
// * On peut passer commande ?
$this->authorize('orderThings', Order::class);
return $next($request);
});
}
// * --- TUNNEL DE COMMANDE ---
/**
* Récapitualtif du panier
*
* * --- 1 ---
*
* @return \Illuminate\Contracts\View\View
*/
public function basketRecap()
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertBasketHasMinAmount();
$this->setOrderProcessStep(OrderProcess::reviewBasket());
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$messages = [];
$basket = $this->filterBasketKeepOnlyOrderableItems($basket, $messages);
if (!empty($messages)) {
Session::flash('warning', \collect($messages)->implode(",\n"));
}
$basketItems = ToolboxHelper::cartToList($basket);
return view('theme::pages.order-tunnel.basket-recap', compact(
'basket',
'basketItems'
));
}
/**
* Select shipping Mode And create Session Order
*
* * --- 2 ---
*
* @return \Illuminate\Contracts\View\View
*/
public function shippingMode()
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertBasketHasMinAmount();
$this->setOrderProcessStep(OrderProcess::shippingMethod(), OrderProcess::reviewBasket());
// * CREATE ORDER
/** @var \App\Models\Customer\Customer $customer */
$customer = auth('frontend')->user();
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
/** @var \App\Models\Shop\Order|null $order */
$order = Order::getCurrentOrder();
// * If there was a previous order that was not ended use the ID.
/** @var \App\Models\Shop\Order */
$order = $order ?? new Order();
$order->mail_sent = false;
$order->delivery_status = DeliveryStatus::pending();
$order->payment_status = PaymentStatus::pending();
$order->validation_status = ValidationStatus::pending();
$order->assignBasket($basket);
$order->customer()->associate($customer);
// * Save Session Order
Session::put('currentOrder', $order);
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\Shop\DeliveryMode> */
$deliveryModes = app('DeliveryModesGlobal');
return \view('theme::pages.order-tunnel.shipping-mode', \compact('deliveryModes'));
}
/**
* Save shipping Mode
*
* * --- 2 ---
*
* @param \App\Http\Requests\Fo\Orders\ShippingModeFormRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function shippingModeSubmit(ShippingModeFormRequest $request)
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertBasketHasMinAmount();
$this->assertDoesHaveCurrentOrder();
$this->setOrderProcessStep(OrderProcess::shippingMethod(), OrderProcess::reviewBasket());
/** @var \App\Models\Shop\DeliveryMode $mode */
if (!($mode = DeliveryMode::availables()->where('mode', $request->mode)->first())) {
$e = new \RuntimeException('Mode de livraison non disponible', 1);
report($e);
Session::flash('error', trans('Mode de livraison non disponible'));
return \back()->withException($e);
}
self::allowTimeSlotSelect();
/** @var \App\Models\Shop\Order $order */
$order = Order::getCurrentOrder();
$order->deliveryMode()->associate($mode);
Session::put('currentOrder', $order);
return \response()->redirectToRoute('order-tunnel.shipping');
}
/**
* Shipping informations
*
* * --- 3 ---
*
* ! Pour - Click and collect
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @throws \RuntimeException If delivery mode is unhandled.
*/
public function shipping()
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertBasketHasMinAmount();
$this->assertDoesHaveCurrentOrder();
$this->assertDeliveryModeIsValid();
$this->setOrderProcessStep(OrderProcess::shippment(), OrderProcess::shippingMethod());
/** @var \App\Models\Shop\Order $order */
$order = Order::getCurrentOrder();
// * Switch Delivery modes
// * Pour afficher formulaire qui devra retourner une adresse 'shipping'
/**
* * Switch Delivery modes
* - Pour afficher formulaire qui devra retourner une adresse 'shipping'.
* - Qui devra fournir un timeslot dans le cas de clickAndCollect.
*/
/** @var \App\Enums\Order\DeliveryModeEnum|null */
$delivery_mode = \optional($order->deliveryMode)->mode;
switch ($delivery_mode) {
case DeliveryModeEnum::inShop():
// * 1 Select timeslot if not already done.
if (!$order->timeslot()->first() or !self::allowedTimeSlotSelect()) {
$shops = Shop::all()->map(function (Shop $shop) {
return new ShopResource($shop);
});
/** @var \App\Models\Shop\Timeslot|null */
$timeslot = $order->timeslot;
/** @var \App\Models\Shop\Shop|null */
$shop = $timeslot ? $timeslot->shop : null;
View::share('deliveryModePickSlot', Session::get('deliveryModePickSlot'));
return \view('theme::pages.order-tunnel.pick-slot', \compact(
'order',
'shops',
'timeslot',
'shop'
));
}
/** @var \App\Models\Customer\Customer */
$customer = auth('frontend')->user();
return \view('theme::pages.order-tunnel.shipping', [
'order' => $order, 'customer_info' => $customer->api_data
]);
default:
throw new \RuntimeException('Unhandled delivery mode ');
}//end switch
throw new \RuntimeException('This should never happen');
}
/**
* Save and validate timeslot
*
* * --- 3 ---
*
* @param \App\Http\Requests\Fo\Orders\SetOrderTimeSlotRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Validation\ValidationException If Creneau is not available anomore.
*/
public function validateSelectedTimeSlot(SetOrderTimeSlotRequest $request)
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertBasketHasMinAmount();
$this->assertDoesHaveCurrentOrder();
$this->setOrderProcessStep(OrderProcess::shippment(), OrderProcess::shippingMethod());
/** @var \App\Models\Shop\Order $order */
$order = Order::getCurrentOrder();
/** @var \App\Models\Shop\Timeslot|null $timeslot */
$timeslot = $order->timeslot;
/** @var \App\Models\Shop\Shop */
$shop = Shop::query()->where('id', $request->shop_id)->firstOrFail();
$creneau = Carbon::createFromFormat('Y-m-d H:i', "{$request->selected_day} {$request->selected_time}");
$uuidToIgnore = $order->id ? Uuid::fromString($order->id) : null;
// ? The time slot is different than the already selecte one and the creneau is available ?
if (
$timeslot and
$timeslot->slot->notEqualTo($creneau) and
!$shop->isCreneauAvailable($creneau, $uuidToIgnore)
) {
throw ValidationException::withMessages([
'error' => 'Ce créneau n\'est plus disponible'
]);
}
$this->assertDeliveryModeIsValid();
DB::transaction(function () use ($order, $shop, $creneau) {
// * Assign order id we would use, if none already.
if (!$order->id) {
$order->id = Str::uuid();
}
// * Remove other timeslots for this order.
Timeslot::query()->where('order_id', $order->id)->forceDelete();
/** @var \App\Models\Shop\Timeslot */
$timeSlot = Timeslot::query()->whereHas('order', function (Builder $q) use ($order) {
$q->where('id', $order->id);
})->firstOrNew();
$timeSlot->order()->associate($order);
$timeSlot->shop()->associate($shop);
$timeSlot->slot = $creneau;
$timeSlot->save();
$order->timeslot()->save($timeSlot);
$order->setRelation('timeslot', $timeSlot);
Session::put('currentOrder', $order);
self::allowTimeSlotSelect(true);
});
return response()->redirectToRoute('order-tunnel.shipping');
}
/**
* SaveShippingAdress
*
* * --- 3 ---
*
* @param \App\Http\Requests\Fo\Orders\ShippingFormRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \RuntimeException If somehow no email can be found.
*/
public function shippingSubmit(ShippingFormRequest $request)
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertDoesHaveCurrentOrder();
$this->assertBasketDidNotChange();
$this->assertBasketHasMinAmount();
$this->assertDeliveryModeIsValid();
$this->setOrderProcessStep(OrderProcess::shippment(), OrderProcess::shippingMethod());
/** @var \App\Models\Shop\Order $order */
$order = Order::getCurrentOrder();
$order->fill($request->validated());
/** @var object */
$billing = $order->billing;
$billing->country = Country::france();
$order->billing = $billing;
self::allowTimeSlotSelect();
// Fetch right email to send in.
$order->email = $order->billing->email ??
$order->shipping->email ??
$order->customer->api_data->email;
if (!$order->email) {
throw new \RuntimeException('Somehow no email was provided');
}
$this->fillShippingFromShopInfo($order);
// * Allow timeslot select
Session::put('currentOrder', $order);
return \response()->redirectToRoute('order-tunnel.review');
}
/**
* Valider le panier avant achat.
*
* * --- 4 ---
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function review()
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertBasketHasMinAmount();
$this->assertDoesHaveCurrentOrder();
$this->assertDeliveryModeIsValid();
$this->setOrderProcessStep(OrderProcess::paymentReview(), OrderProcess::shippment());
self::allowTimeSlotSelect();
/** @var \App\Models\Customer\Customer $customer */
$customer = auth('frontend')->user();
/** @var \App\Models\Shop\Order $order */
$order = Order::getCurrentOrder();
// * Order completed email already sent.
if ($order->mail_sent) {
self::destroyCurrentBasket();
$message = trans(
'Votre commande est déjà prise en compte, veuillez vérifier votre boite e-mail ou votre espace client.'
);
return \view('theme::pages.order-tunnel.done', \compact('order', 'message'));
}
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$availablePaymentModes = app('PaymentModesGlobal')->where('status', true);
return view('theme::pages.order-tunnel.review', \compact('basket', 'order', 'availablePaymentModes'));
}
/**
* Setup Order for submit
*
* * --- 4 ---
*
* @param \App\Http\Requests\Fo\Orders\CheckoutRequest $request
* @return \Symfony\Component\HttpFoundation\Response
* @throws \RuntimeException If payment method is unhandled somehow.
*/
public function reviewSubmit(CheckoutRequest $request): Response
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertDoesHaveCurrentOrder();
$this->assertDeliveryModeIsValid();
$this->setOrderProcessStep(OrderProcess::paymentReview(), OrderProcess::shippment());
/** @var \App\Models\Customer\Customer $customer */
$customer = auth('frontend')->user();
// * Force clone here to prevent session save model state (If something goes wrong).
/** @var \App\Models\Shop\Order $order */
$order = clone Order::getCurrentOrder();
// * Order completed email already sent.
if (!$order->exists and $order->mail_sent) {
self::destroyCurrentBasket();
$message = trans(
'Votre commande est déjà prise en compte, veuillez vérifier votre boite e-mail ou votre espace client.'
);
return \view('theme::pages.order-tunnel.done', \compact('order', 'message'));
}
/** @var \App\Enums\Order\PaymentModeEnum */
$paymentMode = $request->modeEnum;
// * Check Payment mode
Gate::authorize('isAvailable', [PaymentMode::class, $paymentMode]);
// * Get Transaction
$transaction = clone (Transaction::getCurrentTransaction() ??
Transaction::getNewInstance(
$paymentMode,
TransactionType::debit(),
$order->basket,
$order->total_ttc,
));
if ($transaction->exists) {
$transaction->refresh();
}
// * If transaction is already payed.
if ($transaction->status->equals(TransactionStatus::validated())) {
// ! Prevent double checkout (double transaction), just let IPN (webhooks) handle this.
$e = new \RuntimeException('La transaction a déjà été traitée');
report($e);
self::destroyCurrentBasket();
abort(500, trans($e->getMessage()));
}
// * If previously failed get a new one.
if ($transaction->status->equals(TransactionStatus::failed())) {
$transaction = Transaction::getNewInstance(
$paymentMode,
TransactionType::debit(),
$order->basket,
$order->total_ttc,
);
}
// * If customer switched transaction mode.
if (!$transaction->payment_mode->equals($paymentMode)) {
// * Reuse previous
/** @var \App\Models\Payment\Transaction $transaction */
$transaction = Transaction::query()->whereHas('order', function (Builder $q) use ($order) {
$q->where('id', $order->id);
})->where('payment_mode', $paymentMode)->where('status', TransactionStatus::pending())
->first() ?? Transaction::getNewInstance(
$paymentMode,
TransactionType::debit(),
$order->basket,
$order->total_ttc,
);
// * Remove other pending in shop transactions
Transaction::query()->whereHas('order', function (Builder $q) use ($order) {
$q->where('id', $order->id);
})->where('status', TransactionStatus::pending())->where('id', '!=', $transaction->id)
->where('payment_mode', PaymentModeEnum::inShop())->delete();
}
// * If payment mode is boutique just use the previous transaction if there was one.
/** @var \App\Models\Payment\Transaction $transaction */
if ($transaction->payment_mode->equals(PaymentModeEnum::inShop())) {
/** @var \App\Models\Payment\Transaction $transaction */
$transaction = Transaction::query()->whereHas('order', function (Builder $q) use ($order) {
$q->where('id', $order->id);
})->where('payment_mode', PaymentModeEnum::inShop())
->where('status', TransactionStatus::pending())->first() ?? $transaction;
}
// * Checkout using selected Payment system.
/** @var \App\Models\Payment\Transaction $transaction */
return $transaction->getAdapter()->checkout(
$customer,
$order,
$transaction,
// * When succeeded
$this->checkoutSucceeded(),
// ! If failed.
function () {
throw ValidationException::withMessages([
'error' => trans('Une erreur est survenue, veuillez réssayer')
]);
}
);
}
/**
* Checkout succeeded event
*
* @return \Closure
*/
private function checkoutSucceeded(): \Closure
{
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
return function (Customer $customer, Order $order, Transaction $transaction): void {
// * Associate transaction to order
$transaction->order()->associate($order);
// * Enregistrement commande avant checkout.
if (!$order->save() or !$transaction->save()) {
report(new \RuntimeException("Order saving error happened."));
throw ValidationException::withMessages([
'error' => trans('Une erreur est survenue, veuillez réssayer')
]);
}
// * Save Transaction
Session::put('currentTransaction', $transaction);
// * Save Order
Session::put('currentOrder', $order);
};
}
/**
* Redirection après paiement et validation
*
* * --- 5 ---
*
* ! Middleware Url signée ici que pour in shop.
*
* * La méthode sers a récupérer la session après redirection du module de paiement.
*
* @param \App\Models\Payment\Transaction $transaction
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function checkout(Transaction $transaction)
{
/** @var \App\Models\Customer\Customer|null $customer */
$customer = auth('frontend')->user();
/** @var \App\Models\Shop\Order|null $order */
$order = Order::getCurrentOrder();
/** @var \App\Models\Payment\Transaction|null $sessionTransaction */
$sessionTransaction = Transaction::getCurrentTransaction();
if (!$customer or !$order or !$sessionTransaction) {
\redirect()->route('order-tunnel.review')
->with('warning', trans('La session est requise'))
->throwResponse();
}
// ? Mismatch between url transaction and session transaction ?
// * Needed for stripe IP in route anyway.
if ($sessionTransaction->id !== $transaction->id) {
\redirect()->route('order-tunnel.review')
->with('error', trans('Erreur mauvaise transaction'))
->throwResponse();
}
// * Force clone here to prevent session save model state (If something goes wrong).
$order = clone $order;
$transaction = clone $transaction;
if (!($transaction = $transaction->getAdapter()->checkoutIsResponseIsValid($customer, $transaction))) {
\abort(400, trans('Impossible de valider la transaction'));
}
$transaction->saveOrFail();
$transaction->refresh();
$transaction->mutateOrderStatus();
$order->refresh();
OrderIsCreated::dispatch($transaction->order);
// * Save Transaction
Session::put('currentTransaction', $transaction);
// * Save Order
Session::put('currentOrder', $order);
return view('back::pages.order-tunnel-checkout');
}
/**
* Redirection après paiement et validation
*
* * --- 5 ---
*
* * La méthode sers a récupérer la session après redirection du module de paiement.
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function checkoutVerify()
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertDoesHaveCurrentOrder();
$this->assertDoesHaveCurrentTransaction();
if (!$this->checkThatActualProcessStepIs(OrderProcess::paymentReview())) {
\redirect()->route('order-tunnel.review')
->with('warning', trans('Étape précédente requise'))
->throwResponse();
}
return DB::transaction(function () {
/** @var \App\Models\Customer\Customer $customer */
$customer = auth('frontend')->user();
// * Force clone here to prevent session save model state (If something goes wrong).
/** @var \App\Models\Shop\Order $order */
$order = clone Order::getCurrentOrder();
// * Force clone here to prevent session save model state (If something goes wrong).
/** @var \App\Models\Payment\Transaction $transaction */
$transaction = clone Transaction::getCurrentTransaction();
$transaction->refresh();
$message = trans('Une fois votre commande validée, vous recevrez un email de confirmation.');
// * Si le payment ne se fait pas en boutique est que la transaction n'est pas validée.
// FIXME: Validate Stripe only by IPN ?
if (
$transaction->payment_mode->equals(PaymentModeEnum::stripe()) and
!$transaction->status->equals(TransactionStatus::validated())
) {
\redirect()->route('order-tunnel.review')
->with('error', trans('Le payment n\'as pu être validé'))
->throwResponse();
}
if ($transaction->payment_mode->equals(PaymentModeEnum::stripe())) {
$message = trans('Votre commande à été validée, vous allez recevoir un email de confirmation.');
}
if ($transaction->payment_mode->equals(PaymentModeEnum::systemPay())) {
$message = trans('Une fois votre commande validée, vous recevrez un email de confirmation.');
}
OrderIsPaid::dispatchIf(
!$transaction->payment_mode->equals(PaymentModeEnum::inShop()) and
$transaction->status->equals(TransactionStatus::validated()),
$transaction->order,
$transaction
);
self::destroyCurrentBasket();
return view('theme::pages.order-tunnel.done', \compact('order', 'message'));
});
}
// * --- FIN TUNNEL DE COMMANDE ---
// * PRIVATE METHODS
/**
* Allow timeslot select in the tunnel even if already selected
*
* @param boolean $disallow To force disallow timeslot picker select.
* @return void
*/
private function allowTimeSlotSelect(bool $disallow = false): void
{
// * Allow timeslot select
Session::put('deliveryModePickSlot', $disallow);
}
/**
* Is allowed to select timesSlot ?
*
* @return boolean
*/
private static function allowedTimeSlotSelect(): bool
{
return (bool)Session::get('deliveryModePickSlot');
}
/**
* Assert user does not have an empty basket
* or redirect with warning
*
* @return void
*/
private function assertDoesHaveCurrentOrder()
{
if (
$order = Order::getCurrentOrder() and
$order->payment_status and
$order->payment_status->equals(PaymentStatus::payed())
) {
self::destroyCurrentBasket();
$message = trans(
'Votre commande est déjà validée, veuillez vérifier votre boite e-mail ou votre espace client.'
);
\response()->view('theme::pages.order-tunnel.done', \compact('order', 'message'))->throwResponse();
}
if (!$order) {
\redirect()->route('order-tunnel.basket.recap')->throwResponse();
}
}
/**
* Delete order steps, removes basket, ends tunnel transaction.
*
* @return void
*/
public static function destroyCurrentBasket()
{
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$basket->destroy();
// * Remove timeslot if order is not saved into DB.
/** @var \App\Models\Shop\Order|null */
if ($order = Session::get('currentOrder') and $order->timeslot and !$order->exists) {
$order->timeslot->forceDelete();
}
Session::remove('currentOrder');
Session::remove('currentTransaction');
Session::remove('PreviousOrderStep');
Session::remove('OrderStep');
Session::remove('deliveryModePickSlot');
Session::remove('basketFingerPrint');
}
/**
* Assert user does not have an empty basket
* or redirect with warning
*
* @return void
*/
private function assertDoesNotHaveEmptyBasket(): void
{
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
if (!$basket->count()) {
\redirect()->route('home')
->with('warning', trans('Vous n\'avez aucun produit dans votre panier'))
->throwResponse();
}
}
/**
* Assert user does not have an empty basket
* or redirect with warning
*
* @return void
*/
private function assertBasketDidNotChange(): void
{
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$actual_finger_print = hash('sha256', \json_encode($basket->content()->jsonSerialize()));
$session_finger_print = Session::get('basketFingerPrint');
Session::put('basketFingerPrint', $actual_finger_print);
if ($session_finger_print !== $actual_finger_print) {
$this->allowTimeSlotSelect(true);
\redirect()->route('order-tunnel.basket.recap')
->with('warning', trans('Votre panier à été modifié'))
->throwResponse();
}
}
/**
* Assert basket has the minimal amount required for payment
*
* @return void
*/
private function assertBasketHasMinAmount(): void
{
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$sampleOrder = new Order();
$sampleOrder->assignBasket($basket);
$minAmountForTransaction = \collect(PaymentModeEnum::toArray())
->max(function (PaymentModeEnum $paymentMode) {
return $paymentMode->minAmount;
});
if ($sampleOrder->total_ttc < $minAmountForTransaction) {
\redirect()->route('home')
->with('warning', trans('Le montant minimal pour une commande est de :minAmount', [
'minAmount' => ToolboxHelper::fmtCurrency($minAmountForTransaction)
]))->throwResponse();
}
}
/**
* Assert user does not have an empty basket
* or redirect with warning
*
* @return void
*/
private function assertDoesHaveCurrentTransaction()
{
$transaction = Transaction::getCurrentTransaction();
if (
!$transaction or ($transaction->status and
$transaction->status->equals(TransactionStatus::failed())
)
) {
\redirect()->route('order-tunnel.review')
->with('warning', trans('La transaction a échoué'))
->throwResponse();
}
}
/**
* Assert delivery mode si still available.
*
* @return void
*/
private function assertDeliveryModeIsValid(): void
{
$order = Order::getCurrentOrder();
if (!Gate::check('orderUsingDeliveryMode', $order)) {
$this->allowTimeSlotSelect(true);
\redirect()->route('order-tunnel.shipping-mode')
->with('warning', trans('Le mode de livraison n\'est pas disponible'))
->throwResponse();
}
}
/**
* Validate Process step
*
* @param \App\Enums\Order\OrderProcess $step
* @param \App\Enums\Order\OrderProcess|null $assertDid
* @return void
* @throws \Illuminate\Validation\ValidationException If previous was not done.
*/
private function setOrderProcessStep(OrderProcess $step, OrderProcess $assertDid = null): void
{
if ($assertDid) {
if (!Session::get('PreviousOrderStep')) {
Session::put('PreviousOrderStep', collect());
}
/** @var \Illuminate\Support\Collection */
$previousSteps = Session::get('PreviousOrderStep');
if (Session::get('OrderStep') < $step) {
/** @var \App\Enums\OrderProcess */
$previousStep = Session::get('OrderStep');
$previousSteps->put($previousStep->value, $previousStep);
Session::put('PreviousOrderStep', $previousSteps);
}
$this->assertDidProcessStep($assertDid);
} //end if
Session::put('OrderStep', $step);
}
/**
* Assert the the step is done.
*
* @param \App\Enums\Order\OrderProcess $assertDid
* @return void
* @throws \Illuminate\Validation\ValidationException If previous was not done.
*/
private function assertDidProcessStep(OrderProcess $assertDid): void
{
if (!Session::get('PreviousOrderStep')) {
Session::put('PreviousOrderStep', collect());
}
/** @var \Illuminate\Support\Collection */
$previousSteps = Session::get('PreviousOrderStep');
if (!$previousSteps->count()) {
\redirect()->route('home')
->with('warning', trans('Vous devez d\'abord valider votre panier'))
->throwResponse();
}
if (!$previousSteps->contains($assertDid)) {
throw ValidationException::withMessages(['Étape précédente requise']);
}
}
/**
* Assert the actual step is.
*
* @param \App\Enums\Order\OrderProcess $assertActual
* @return boolean
* @throws \Illuminate\Validation\ValidationException If previous was not done.
*/
private function checkThatActualProcessStepIs(OrderProcess $assertActual): bool
{
/** @var \App\Enums\OrderProcess|null */
$orderStep = Session::get('OrderStep');
return $orderStep and $orderStep->equals($assertActual);
}
/**
* Fill order shipping with in shop infos
*
* @param \App\Models\Shop\Order $order
* @return void
* @throws \Illuminate\Validation\ValidationException Si le créneau de reservation n'est plus trouvable.
*/
private function fillShippingFromShopInfo(Order $order)
{
if ($order->deliveryMode->mode->equals(DeliveryModeEnum::inShop())) {
/** @var \App\Models\Shop\Timeslot|null */
if (!($timeslot = $order->timeslot()->first())) {
throw ValidationException::withMessages([
'error' => trans('Créneau de réservation introuvable')
]);
}
$order->shipping = [
'firstname' => $timeslot->shop->name,
'lastname' => '',
'email' => null,
'phone' => $timeslot->shop->phone,
'line1' => $timeslot->shop->address,
'postal_code' => $timeslot->shop->postal_code,
'city' => $timeslot->shop->city,
'country' => $timeslot->shop->country
];
}
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\Merchandise\Product;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class ProductController extends Controller
{
/**
* One product page
*
* @param \App\Models\Merchandise\Product $productSlug
* @return \Illuminate\Contracts\View\View
*/
public function product(Product $productSlug)
{
return view('theme::pages.products.show', ['productModel' => $productSlug]);
}
/**
* All products.
*
* @return \Illuminate\Contracts\View\View
*
*/
public function products()
{
$products = Product::query()->where('status', 1)->with(['discount'])->paginate(12);
return view('theme::pages.products.index', compact('products'));
}
/**
* Product card.
*
* @param \App\Models\Merchandise\Product $product
* @return \Illuminate\Contracts\View\View
*/
public function ajaxProductsCard(Product $product)
{
return view('theme::partials.products.card', ['product' => $product]);
}
/**
* Recherche par nom de produit.
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
*/
public function searchProducts(Request $request)
{
$filter = \htmlentities($request->query('filter'));
$products = Product::query()->where('status', true)
->when(!empty($filter), function (Builder $q) use ($filter) {
$q->where('name', 'like', "%{$filter}%");
})->with(['discount'])->paginate(12);
$search_title = 'Recherche : ' . $filter;
return view('theme::pages.search', compact('products', 'search_title', 'filter'));
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\Merchandise\Product;
use App\Models\Merchandise\Range;
use Illuminate\Database\Eloquent\Builder;
class RangeController extends Controller
{
/**
* All ranges.
*
* @return \Illuminate\Contracts\View\View
*/
public function ranges()
{
// $ranges shared in AppServiceProvider.
return view('theme::pages.ranges.index');
}
/**
* One Range page.
*
* @param \App\Models\Merchandise\Range $rangeSlug
* @return \Illuminate\Contracts\View\View
*/
public function range(Range $rangeSlug)
{
$products = Product::query()->whereHas('range', function (Builder $q) use ($rangeSlug) {
$q->where('id', $rangeSlug->id);
})->where('status', true)->with('discount')->paginate(9);
// * Find promo within range.
$offres = Product::query()->whereHas('range', function (Builder $q) use ($rangeSlug) {
$q->where('id', $rangeSlug->id);
})->where('status', true)->has('discount')->with('discount')->get();
return view('theme::pages.ranges.show', [
'range' => $rangeSlug,
'products' => $products,
'offres' => $offres
]);
}
/**
* Range card.
*
* @param \App\Models\Merchandise\Range $range
* @return \Illuminate\Contracts\View\View
*/
public function ajaxRangesCard(Range $range)
{
return view('theme::partials.ranges.card', ['range' => $range]);
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Models\Merchandise\Product;
use App\Models\Site\Slide;
use App\Models\Site\StaticPage;
use Illuminate\Http\Request;
class StaticPageController extends Controller
{
/**
* Display static page type home
*
* @return \Illuminate\Contracts\View\View
*/
public function home()
{
$content = StaticPage::getPageOfType(StaticPageType::home());
// * Star products.
$products = Product::where('status', true)->where('star', true)->get();
$slides = Slide::query()->where('status', true)->get();
return view(
'theme::pages.home',
compact('content', 'products', 'slides')
);
}
/**
* Display a static page
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function displayCrudStaticPageIndex()
{
return \view('theme::pages.static-page-index');
}
/**
* Display a static page
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Site\StaticPage $static_page_front
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @throws \RuntimeException If the static page type is not handled.
*/
public function displayStaticPage(Request $request, StaticPage $static_page_front)
{
$static_page = $static_page_front;
switch ($static_page->type) {
case StaticPageType::contact():
return app(ContactController::class)->contact($request);
case StaticPageType::legals():
case StaticPageType::sellconditions():
case StaticPageType::confidentiality():
case StaticPageType::faq():
return view('theme::pages.static-page', compact('static_page'));
case StaticPageType::crud():
case StaticPageType::home():
default:
if (\config('app.debug')) {
throw new \RuntimeException("Unhandled static page {$static_page->type->label}");
}
\abort(404);
}
}
/**
* Display a crud static page
*
* @param \App\Models\Site\StaticPage $static_page
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @throws \RuntimeException If the static page type is not handled.
*/
public function displayCrudStaticPage(StaticPage $static_page)
{
if (!$static_page->type->equals(StaticPageType::crud())) {
if (\config('app.debug')) {
throw new \RuntimeException("Unhandled static page {$static_page->type->label}");
}
\abort(404);
}
return view('theme::pages.static-page', compact('static_page'));
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\Merchandise\Product;
use App\Models\Merchandise\Tag;
class TagController extends Controller
{
/**
* All tags.
*
* @return \Illuminate\Contracts\View\View
*
*/
public function tags()
{
$tags = Tag::query()->where('status', true)
->with(['products'])->paginate(12);
return view('theme::pages.tags.index', compact('tags'));
}
/**
* One tag page,
* products filtered by tag
*
* @param \App\Models\Merchandise\Tag $tag
* @return \Illuminate\Contracts\View\View
*/
public function tag(Tag $tag)
{
$products = Product::query()
->where('status', true)
->with(['discount'])
->whereHas('tags', function ($q) use ($tag) {
$q->where('id', $tag->id);
})->paginate(12);
return view('theme::pages.tags.show', compact('products', 'tag'));
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Models\Shop\Order;
use App\Models\Shop\Shop;
use Ramsey\Uuid\Uuid;
class TimeSlotController
{
/**
* Récupérer tout les créneaux disponible à partir de
* maintenant pour un magasin sur les jours à venir.
*
* @param \App\Models\Shop\Shop $shop Le magasin concerné.
* @return array
* @throws \RuntimeException Si la période demandé est incohérente.
*/
public function getAvailableCreneauxByShop(Shop $shop)
{
/** @var \App\Models\Shop\Order $order */
$order = Order::getCurrentOrder();
return $shop->getAvailableCreneaux(null, $order->id ?
Uuid::fromString($order->id) : null);
}
}

View file

@ -1,76 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Content;
use App\Models\Product;
use App\Models\Slider;
use App\Models\Range;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\SingleOptin;
class HomeController extends Controller
{
/**
* Display homepage
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function index(Request $request)
{
// @phpcs:enable
$content = Content::where('key', 'accueil')->first();
// Star products.
$products = Product::where('status', 1)->where('star', 1)->get();
$slider = Slider::where('slug', 'home')->first();
$slides = $slider->showableSlides;
$quickorder_ranges = Range::with('products')->get();
foreach ($quickorder_ranges as $range) {
foreach ($range->products as $product) {
$product->calculatedPrices = $product->getCalcPrices();
}
}
return view('home', compact('content', 'products', 'slides', 'quickorder_ranges'));
}
/**
* Call the CRM to add a single optin
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
*/
public function addOptIn(Request $request)
{
$validator = Validator::make($request->all(), ['email' => 'required|email']);
if ($validator->fails()) {
return __('L\'adresse email est incorrecte');
}
try {
SingleOptin::setStatus($request->email, true);
return trans('Votre inscription à la newsletter a été prise en compte');
} catch (ApiErrorException $e) {
// * Il y a deja un customerInfo avec optin a true pour ce customer
if ($e->getApiError()->equals(ApiError::singleOptinCustomerExists())) {
return trans('Vous êtes déjà inscrit avec cette adresse email');
}
report($e);
return trans('Une erreur est survenue, merci de réessayer ultérieurement');
} catch (CrmException $e) {
report($e);
return trans('Une erreur est survenue, merci de réessayer ultérieurement');
}
return trans('Une erreur est survenue, merci de réessayer ultérieurement ');
}
}

Some files were not shown because too many files have changed in this diff Show more