Compare commits

..

394 Commits

Author SHA1 Message Date
Valentin Lab
ef1964d472 fix: prevent error 500 on article pages 2025-11-03 11:35:16 +01:00
Valentin Lab
abb32e32b9 fix: add "Bientôt disponible" box on public product page without prices 2025-11-03 11:27:47 +01:00
Valentin Lab
8c29459489 new: make the debug info available to all backoffice users with helpful links 2025-11-03 11:23:58 +01:00
Valentin Lab
accb052f5c fix: repair price appearance on all articles 2025-11-03 11:23:24 +01:00
Valentin Lab
d5f095b5e5 fix: remove non-visible article from research results 2025-11-03 09:20:53 +01:00
Valentin Lab
fd628f3f95 fix: prevent error 500 on creation of new backoffice user 2025-11-03 09:09:36 +01:00
Valentin Lab
a10f0b35d9 fix: focus invalid field on error in article form 2025-10-15 14:49:41 +02:00
Valentin Lab
858421a9eb fix: prevent broken link upon thumbnail in variety list when having uploaded a PNG file 2025-10-15 14:48:51 +02:00
Valentin Lab
158bc4fd57 fix: provide correct temporary directory outside of `vendor/` 2025-10-15 14:08:16 +02:00
Valentin Lab
b7e3eefed6 new: allow to delete seuil lines in price-list's pice modal 2025-10-15 13:17:54 +02:00
Valentin Lab
67e4346c68 fix: pkg: do not create bogus `{cache,views,sessions}` directory in prod export 2025-10-15 12:46:01 +02:00
Valentin Lab
9ce62e82e5 fix: allow saving list-price's price seuil if seuil is unset or 0 2025-10-15 12:22:36 +02:00
Valentin Lab
7e93219774 fix: allow to re-use a deleted ref in articles 2025-10-15 12:05:16 +02:00
Valentin Lab
29f46b7287 fix: enable saving in price-list's price edit modal 2025-10-15 11:57:38 +02:00
Valentin Lab
1f02c932a0 fix: make varieties creation form avoid error 500 on save 2025-10-15 11:57:38 +02:00
Valentin Lab
7d8bd8c372 fix: make form submit apply modification on existing article 2025-10-15 11:57:38 +02:00
Valentin Lab
f4bd4ddf24 fix: prevent err 500 upon species edit form opening 2025-10-15 11:57:28 +02:00
Valentin Lab
1f7098d55b fix: repair link made by `asset() in blade template when working on http`
The forcing is useless, we are forcing links through many other
ways. I need to test aspects of deployments on my laptop to mimic
production deployment without this hassle.
2025-10-11 05:32:01 +02:00
Valentin Lab
1867e75177 new: doc: added `AGENTS.md` and small addition of a french paragraph 2025-10-10 08:20:13 +02:00
Valentin Lab
d502882052 fix: add delivery cost on load if delivery is selected 2025-10-05 12:38:19 +02:00
Valentin Lab
a5b2196b32 fix: make the selected channel apply changes to product each time 2025-10-05 12:33:08 +02:00
Valentin Lab
cc8dfa29b4 fix: display only delivery types that have a price and auto-select first 2025-10-05 10:09:03 +02:00
Valentin Lab
62bce92d6d fix: make delivery option on checkout stick to the current sale channel 2025-10-05 09:56:33 +02:00
Valentin Lab
8d130b9741 new: add channel management 2025-10-05 09:39:27 +02:00
Valentin Lab
2d7436a12b fix: make sale channel description field editable 2025-10-05 05:26:20 +02:00
Valentin Lab
f25a62ed26 new: make admin delivery edition can toggle off public and active states 2025-10-05 03:32:08 +02:00
Valentin Lab
36764f2647 fix: make save button avoid error 500 in delivery method admin page 2025-10-05 03:28:03 +02:00
Valentin Lab
e37cad6699 new: make the eye icon work to see an invoice in admin customer view 2025-10-04 15:37:28 +02:00
Valentin Lab
ae7f8ed2c9 fix: remove 404 about javascript file in admin console 2025-10-04 14:39:07 +02:00
Valentin Lab
a3a86f4b2f new: keep cart when login in 2025-10-04 14:13:48 +02:00
Valentin Lab
9c081574c8 new: make click in choices of search box load the page of the product 2025-10-04 13:54:21 +02:00
Valentin Lab
11edccad02 fix: make invoices creation resistant to missing address if this still happens 2025-10-04 12:55:11 +02:00
Valentin Lab
7c796802be new: make invoice still keep the old addresses when their address gets deleted in profile 2025-10-04 12:39:13 +02:00
Valentin Lab
5cc43bc889 fix: make the button to add an address unusable when the address form is open 2025-10-04 12:19:24 +02:00
Valentin Lab
f094411f10 new: add persistence of default address selection 2025-10-04 11:59:57 +02:00
Valentin Lab
ccc477f291 new: display the default address checkbox on profile load 2025-10-04 11:59:57 +02:00
Valentin Lab
7217d945a3 fix: make the address appear when added 2025-10-04 11:59:57 +02:00
Valentin Lab
9185269874 fix: prevent deleting last address for each kind 2025-10-04 11:59:57 +02:00
Valentin Lab
e42e3b4c0d fix: prevent 404 when deleting an adress 2025-10-04 11:06:43 +02:00
Valentin Lab
a7ae946797 fix: prevent error 500 on profile edition 2025-10-04 10:51:41 +02:00
Valentin Lab
7a189abf0b fix: move `build directory to resources/shop` 2025-10-04 10:13:38 +02:00
Valentin Lab
34fc1c33bf fix: repair favicon links and provide one from https://www.jardinenvie.com 2025-10-04 09:41:15 +02:00
Valentin Lab
61e34b4f4e fix: finalize payments and clear cart after Paybox success
This captures the Paybox verification flow, duplicate-payment guard, and cart cleanup.
2025-10-04 09:17:53 +02:00
Valentin Lab
7fe2770d45 fix: do not call debugbar if not available (when in prod) 2025-09-29 11:32:19 +02:00
Valentin Lab
0479ae25f8 new: add docker build reproducible code 2025-09-29 11:22:02 +02:00
Valentin Lab
0a45b0c71f fix: pkg: repair grunt 2025-09-25 13:58:09 +02:00
ludo
1cc6cc879e fix: fix 2025-04-21 10:57:31 +02:00
ludo
adbba79bd2 fixes 2025-03-18 13:06:02 +01:00
ludo
592402a6c1 fixes 2025-02-15 12:12:42 +01:00
ludo
d1cc62c9b1 remove browsershot 2025-01-03 16:05:05 +01:00
ludo
a76d81c437 fix contents 2025-01-03 16:04:19 +01:00
ludo
2e71f17856 fix calculation on indirect articles by tags 2025-01-03 14:45:23 +01:00
ludo
df78126b12 refactor scopes 2025-01-03 14:09:22 +01:00
ludo
befaa40b48 fixes 2025-01-03 03:46:45 +01:00
ludo
b3fbfc38e7 fixes 2024-06-17 21:56:42 +02:00
ludo
ee60bac538 fixes on addresses 2024-04-29 22:01:45 +02:00
ludo
c63bb762ed fix on producter 2024-03-11 19:31:22 +01:00
ludo
44bfe7d09c fix on tags 2024-03-11 19:02:57 +01:00
ludo
5db7438c27 fixes on auth customers 2024-03-11 18:48:25 +01:00
ludo
2227242704 fix route 2024-03-11 18:38:23 +01:00
ludo
7b7295aed1 fixes 2024-03-03 22:52:00 +01:00
ludo
26ca3eb3ca Core classes removed by error 2024-03-03 14:53:54 +01:00
ludo
139aeb8074 fixes 2024-03-03 14:44:35 +01:00
ludo
cc411cba68 minor fixes 2024-02-23 08:35:41 +01:00
ludo
fb6da523fa cleaning day 2024-02-22 21:28:33 +01:00
ludo
64a218afc2 coding style 2024-02-22 19:35:51 +01:00
ludo
722ea43bc2 add monitoring 2024-02-22 19:33:56 +01:00
ludo
869b148e20 add seo 2024-02-19 23:51:32 +01:00
ludo
15a6621a56 add shop cart storage 2024-02-07 21:54:58 +01:00
ludo
601b758179 fixes 2024-02-07 21:11:17 +01:00
ludo
e559c785c2 add health route 2024-02-07 21:07:19 +01:00
ludo
329643ce3b add health route 2024-02-07 21:05:48 +01:00
ludo
7bb38071ef add health 2024-02-07 21:01:02 +01:00
ludo
ee9979f547 fix 2024-02-07 20:43:58 +01:00
ludo
c4fda18356 move autocomplete for customer 2024-02-07 20:41:04 +01:00
ludo
8c6e10fb3b force https on docker 2024-02-07 20:31:51 +01:00
ludo
5a400aaedd add restore backup 2024-02-05 22:36:45 +01:00
ludo
23fb8a79ac fix on worker with php 8.2 2024-02-05 22:04:17 +01:00
ludo
67e439f420 fix ports 2024-02-05 21:48:07 +01:00
ludo
df377c4f3f fix on docker 2024-02-05 21:38:40 +01:00
ludo
69264dcf80 fix on docker 2024-02-05 21:34:29 +01:00
ludo
308b6cb349 add watermark on zoom, update display of article nature on shelve (change icon to text 2024-02-04 22:09:53 +01:00
ludo
4c6f9b3b61 add datatbles for invoices, add pdf icon, refactor icons components, add autocomplete on search, adapt searching to meilisearch 2024-02-04 02:51:38 +01:00
ludo
067532b6fc add new search engine 2024-01-31 23:45:58 +01:00
ludo
8eb3104b2a add metrics 2024-01-30 23:24:00 +01:00
ludo
1bc9bf9fe9 add migration to catrt storage, update SCOUT 2024-01-29 23:45:55 +01:00
ludo
53ad10eefa add new metrics, graph metrics, prepare basket to storage 2024-01-29 23:44:49 +01:00
ludo
9fcc81f4d9 add graphs for stats 2024-01-29 22:39:57 +01:00
ludo
75107285e7 Merge branch 'master' of https://gitlab.huma.net/ludo/opensem 2024-01-28 20:28:39 +01:00
ludo
502b71617a fix articles datatables, enhance statistics 2024-01-28 19:56:13 +01:00
Ludovic CANDELLIER
ca474ddadb restrict ports to be compatible with legacy version 2024-01-23 00:01:15 +01:00
Ludovic CANDELLIER
fbe9633651 add certbot 2024-01-22 23:05:39 +01:00
Ludovic CANDELLIER
1677ec6b03 add slave1 2024-01-22 23:04:23 +01:00
Ludovic CANDELLIER
1533c18c54 remove highcharts 2024-01-22 23:01:33 +01:00
Ludovic CANDELLIER
fc5a3186bf remove old models 2024-01-22 22:52:03 +01:00
ludo
0bb75125a7 update 2024-01-22 22:50:56 +01:00
ludo
af5fc8d0ee Merge branch 'master' of https://gitlab.huma.net/ludo/opensem 2024-01-22 22:47:39 +01:00
ludo
17e322cd88 update migrations 2024-01-22 22:47:20 +01:00
Ludovic CANDELLIER
8ceab7e9f5 update docker 2024-01-22 22:14:36 +01:00
ludo
116f289285 refactoring on Articles, minor fixes 2024-01-21 11:42:42 +01:00
ludo
560ef61c9f render invoice in pdf 2024-01-07 23:16:29 +01:00
ludo
4df8628a3e fixing styles 2024-01-07 20:42:37 +01:00
ludo
5144c1f7fd fix devops error 2024-01-05 01:30:46 +01:00
ludo
90b0af5b2d coding styles 2024-01-04 15:54:18 +01:00
ludo
03027cde01 fix on payment by cb 2024-01-04 15:43:02 +01:00
ludo
8a463e7b9e update payments and vat mentions 2023-12-21 23:04:42 +01:00
ludo
643c26d549 fix parameters for invoices 2023-12-21 16:57:40 +01:00
ludo
ee64ae0be7 fix greedy replace 2023-12-12 21:40:24 +01:00
ludo
e0d8106078 minor fixes 2023-12-11 21:09:48 +01:00
ludo
df65516b36 adjust deliveries by customer 2023-12-11 21:07:49 +01:00
ludo
25b78f3380 change homepages to contents, add new methods to deliveries and sale_channels by customer 2023-12-09 23:55:50 +01:00
ludo
2a429e4163 minor fixes 2023-12-09 21:02:28 +01:00
ludo
b5da5fc881 enhance invoice display 2023-12-03 02:20:41 +01:00
ludo
ec509df665 better management of shipping and basket summary display 2023-12-03 00:40:47 +01:00
ludo
4bcfc7bc6d add basket on merchandise form 2023-11-25 20:42:15 +01:00
ludo
9949ae95cf update 2023-11-25 20:23:21 +01:00
ludo
82b864768e fix invoice payment 2023-11-25 19:33:41 +01:00
ludo
34f0b2796f add payments by invoice 2023-11-25 16:21:35 +01:00
ludo
731c31a58c fix on merchandises with validator 2023-11-25 16:21:02 +01:00
ludo
9b18531c83 try to refresh total with recalculation by delivery and delivery_type 2023-11-16 00:00:05 +01:00
ludo
2ebdc5f16b add shipping to order, methods to calculate, little refactoring 2023-11-15 23:20:42 +01:00
ludo
04df068931 fixes 2023-11-14 00:25:58 +01:00
ludo
86b8156e38 little refactoring 2023-11-14 00:11:31 +01:00
ludo
b86b043604 add validator, optimizations 2023-11-13 23:03:12 +01:00
ludo
23ac0cedad fix on weight 2023-11-13 00:40:41 +01:00
ludo
9f90f983ab Fix on addresses 2023-11-13 00:02:21 +01:00
ludo
4ce3d528dd fix on product null 2023-11-08 17:07:53 +01:00
ludo
e42ac75ff7 fix if no icon 2023-11-08 16:55:26 +01:00
ludo
746cf661ce fix basket 2023-10-31 17:05:41 +01:00
ludo
a9432bd3c1 add filter not collapsed if filter is on 2023-10-17 17:47:25 +02:00
ludo
f3b9db1a6f fix on customer auth, fix filters on shelves, refactor for article_nature, add slug 2023-10-17 17:20:30 +02:00
Ludovic CANDELLIER
50d5d6944d Supprimer le fichier .env du répertoire docker 2023-09-13 23:10:04 +02:00
Ludovic CANDELLIER
bc7880b242 add docker version 2023-09-13 23:04:55 +02:00
Ludovic CANDELLIER
5f215cef81 coding style 2023-09-13 22:53:37 +02:00
Ludovic CANDELLIER
da48f41ec0 minor fix on updating status of order, upgrade datatables 2023-09-13 22:17:49 +02:00
Ludovic CANDELLIER
eda2bbf1db adapt display of article_natures 2023-09-12 23:39:28 +02:00
Ludovic CANDELLIER
5e161745bb adapt display of article natures by disponibility of offers in shelve 2023-09-12 23:27:56 +02:00
Ludovic CANDELLIER
a29faabbf2 add methods to get icon on article natures 2023-09-12 23:00:36 +02:00
Ludovic CANDELLIER
470560efb6 refactor datatables admin 2023-08-29 23:31:15 +02:00
Ludovic CANDELLIER
b1d16a7871 refactoring of admin datatables 2023-08-29 22:56:37 +02:00
Ludovic CANDELLIER
496274b4f4 fix orders datatables on profile, fix deliveries for profile (public & active) 2023-08-29 22:15:37 +02:00
Ludovic CANDELLIER
7addea00a2 fixes on login 2023-08-28 22:58:11 +02:00
Ludovic CANDELLIER
f721422abc fixes 2023-08-28 21:48:04 +02:00
Ludovic CANDELLIER
1cba52bb6d fixes, add weight 2023-08-01 21:55:17 +02:00
Ludovic CANDELLIER
902604b9cd fixes 2023-07-16 18:09:40 +02:00
Ludovic CANDELLIER
b8c31f6049 fix shipping 2023-07-16 17:54:44 +02:00
Ludovic CANDELLIER
1675745e2a fixes on bad pint 2023-07-16 15:07:15 +02:00
Ludovic CANDELLIER
0879b0abf0 add shipping rules 2023-07-16 14:45:42 +02:00
Ludovic CANDELLIER
72a7b270f9 add mail tracker 2023-07-04 23:33:13 +02:00
Ludovic CANDELLIER
3d16580bc8 add multiple addresses on customer edition 2023-07-04 23:32:41 +02:00
Ludovic CANDELLIER
d6ab6c73e2 rollback to retrieve buttons 2023-07-04 19:24:31 +02:00
Ludovic CANDELLIER
9f9b7173d7 fixes on mail templates, change order edit layout, add DeliveryTypes, DeliveryTypeCalculations & DeliveryPackages 2023-05-24 23:30:29 +02:00
Ludovic CANDELLIER
c677dbd5fa add package to date scopes & browsershot 2023-05-09 21:51:48 +02:00
Ludovic CANDELLIER
313525a25b finish implementing mails 2023-04-17 00:27:03 +02:00
Ludovic CANDELLIER
24e518fffe add methods to detect distinct product type et article nature on shelve 2023-04-01 22:01:15 +02:00
Ludovic CANDELLIER
7d1a34a12e simplify variables names for templates, refactor to be multi-model 2023-03-28 00:17:04 +02:00
Ludovic CANDELLIER
938d6a9cbd Enhance modal 2023-03-27 23:12:57 +02:00
Ludovic CANDELLIER
0a6b90b434 fix on article based on old merchandise 2023-03-27 21:05:37 +02:00
Ludovic CANDELLIER
20b3521c72 add basket on rows, uniformize baskets 2023-03-21 23:16:47 +01:00
Ludovic CANDELLIER
06c68dd223 fix recalculation on basket, fix quick add on basket 2023-03-21 22:41:48 +01:00
Ludovic CANDELLIER
4f9f9b296d cosmetic fixes, enhance profile, fix mails, ... 2023-03-14 23:33:14 +01:00
Ludovic CANDELLIER
7454411d27 fix active hierarchy in menu and megamenu 2023-03-14 21:46:57 +01:00
Ludovic CANDELLIER
4e69399309 add datatables on orders 2023-02-28 08:42:53 +01:00
Ludovic CANDELLIER
3745abc90b remove buggy package for logging mail / incompatible with laravel 9 2023-02-27 23:16:45 +01:00
Ludovic CANDELLIER
0828ac3377 quick add to basket on shelves 2023-02-27 23:16:15 +01:00
Ludovic CANDELLIER
b3d16a06b0 adapt to laravel 9 2023-02-27 23:15:16 +01:00
Ludovic CANDELLIER
753be00a1e fix modal 2023-02-27 22:02:19 +01:00
Ludovic CANDELLIER
a63618a753 fixe menu 2023-02-27 21:45:28 +01:00
Ludovic CANDELLIER
3287caac54 fix typo 2023-02-17 22:27:07 +01:00
Ludovic CANDELLIER
312f1f4e3d fix typo on Admin 2023-02-17 22:22:04 +01:00
Ludovic CANDELLIER
39e3407ea1 fix on sale_channel get_default 2023-02-17 22:08:13 +01:00
Ludovic CANDELLIER
03a52d504b fix on sale_channel get_default 2023-02-17 22:07:43 +01:00
Ludovic CANDELLIER
c1d7f3fe10 fix if default sale channel empty 2023-02-17 21:47:36 +01:00
Ludovic CANDELLIER
8e571de523 Fix on invoices, add delivery reference, wip on dashboard concurrency requests designed on template 2023-02-17 00:05:03 +01:00
Ludovic CANDELLIER
878ec7a8f2 remove debug mode 2023-02-14 00:30:30 +01:00
Ludovic CANDELLIER
06107cb8fc Add overlay on css, adapt shelves, fix bienvenue mail on laravel 9 methods 2023-02-14 00:20:00 +01:00
Ludovic CANDELLIER
10cebd0955 upgrade to version 9 2023-02-13 23:47:01 +01:00
Ludovic CANDELLIER
0ecc7c73c7 enhance components, add mailtemplate, add traits for translations, for stats 2023-02-13 22:52:39 +01:00
Ludovic CANDELLIER
f2f4788ce1 change icons, css, add routing to merchandise, add mail templater, fixes 2023-02-12 23:34:48 +01:00
Ludovic CANDELLIER
8313e25f2e fix megamenu on hover for yellow background 2023-02-12 00:11:25 +01:00
Ludovic CANDELLIER
cafd0a49e7 change registration or connection in order page, change filter on shelve page, add new api to get article_nature by product_type, css fixes 2023-02-10 23:11:48 +01:00
Ludovic CANDELLIER
405effe43e fix on empty images on merchandise 2023-02-09 23:49:17 +01:00
Ludovic CANDELLIER
4914e0c9c9 add cookie consent, change search for product_type empty 2023-02-07 23:25:59 +01:00
Ludovic CANDELLIER
909336bb8b fix debug mode 2023-02-06 23:52:20 +01:00
Ludovic CANDELLIER
60682f2295 remove order alphabetically because is against reordering tree 2023-02-06 23:48:50 +01:00
Ludovic CANDELLIER
2fc88c6163 fix col, because bug on 2 lines for long menu 2023-02-05 23:22:44 +01:00
Ludovic CANDELLIER
6c60d9a148 fix 2023-02-05 23:16:00 +01:00
Ludovic CANDELLIER
cb488383e0 fix css and html structure 2023-02-05 22:57:47 +01:00
Ludovic CANDELLIER
32291dc44a enhance css 2023-02-05 21:40:05 +01:00
Ludovic CANDELLIER
0123885e03 enhance addresses 2023-01-01 22:45:27 +01:00
Ludovic CANDELLIER
72d989f692 fix on login 2023-01-01 21:24:40 +01:00
Ludovic CANDELLIER
66e0197b50 fix 2022-12-29 19:23:28 +01:00
Ludovic CANDELLIER
bfd30b668e fixes 2022-12-29 17:13:43 +01:00
Ludovic CANDELLIER
28c200fd9f fix design, add addresses 2022-12-29 16:16:09 +01:00
Ludovic CANDELLIER
7819a8e11b fix 2022-12-22 18:17:50 +01:00
Ludovic CANDELLIER
b6821d52a7 fix 2022-12-22 18:16:25 +01:00
Ludovic CANDELLIER
b4d8bab385 fix 2022-12-22 12:10:44 +01:00
Ludovic CANDELLIER
d72dfa5b6b fix description on shelves 2022-12-22 01:17:01 +01:00
Ludovic CANDELLIER
35fcc992ae fix cache css 2022-12-22 01:12:40 +01:00
Ludovic CANDELLIER
0feebca7e0 fixes 2022-12-22 01:09:11 +01:00
Ludovic CANDELLIER
5151f393be fix layout 2022-12-21 18:33:15 +01:00
Ludovic CANDELLIER
73ed46bc28 remove dump 2022-11-24 17:51:37 +01:00
Ludovic CANDELLIER
533b63f8fb fix routes 2022-11-24 17:48:08 +01:00
Ludovic CANDELLIER
966e687509 fix on better routes 2022-11-24 17:41:57 +01:00
Ludovic CANDELLIER
ee1511962b fix 2022-11-24 16:07:40 +01:00
Ludovic CANDELLIER
d62cad1725 fixes on invoices relations and revisions 2022-11-20 00:21:38 +01:00
Ludovic CANDELLIER
0cf5569a4c Merge branch 'master' of https://gitlab.huma.net/ludo/opensem 2022-11-19 23:43:39 +01:00
Ludovic CANDELLIER
73763bb146 fix editing orders 2022-11-19 23:43:12 +01:00
Ludovic CANDELLIER
caee665758 reorganize 2022-11-11 13:24:24 +01:00
Ludovic CANDELLIER
7df2421373 restart 2022-11-11 13:05:40 +01:00
Ludovic CANDELLIER
f89acd9399 [WIP] Working on orders & invoices 2022-08-19 22:04:44 +02:00
Ludovic CANDELLIER
c22b10dd10 [WIP] Finish the order process 2022-08-18 18:20:44 +02:00
Ludo
5cd48c03f9 Add new file 2022-08-02 14:21:10 +00:00
Ludovic CANDELLIER
d423fce4f5 fixes 2022-07-04 00:35:43 +02:00
Ludovic CANDELLIER
573e98a2ce [WIP] Order process with interactive methods 2022-07-03 23:36:33 +02:00
Ludovic CANDELLIER
06cfb92757 [WIP] Order process 2022-07-03 22:38:08 +02:00
Ludovic CANDELLIER
bcb3e15f33 fix on array 2022-07-03 10:48:42 +02:00
Ludovic CANDELLIER
b392b426d5 fix 2022-07-03 09:31:45 +02:00
Ludovic CANDELLIER
e435752484 begin order form with registration 2022-06-26 23:33:39 +02:00
Ludovic CANDELLIER
c2fd71e3d1 fix selector on filters 2022-06-22 22:40:04 +02:00
Ludovic CANDELLIER
e9ab7173f8 Refactor article getter for descriptions & tags, minor fixes on tags 2022-06-22 22:28:18 +02:00
Ludovic CANDELLIER
6b1cc0f045 change for description by level of data 2022-06-16 22:59:26 +02:00
Ludovic CANDELLIER
123b951538 missing file 2022-06-14 22:29:18 +02:00
Ludovic CANDELLIER
9710a7017a fixes 2022-06-14 22:24:24 +02:00
Ludovic CANDELLIER
32044118f3 new routes 2022-06-13 23:30:06 +02:00
Ludovic CANDELLIER
b37321daf8 [WIP] begin of new display for article, shelves 2022-06-13 23:29:05 +02:00
Ludovic CANDELLIER
e31978b1e3 change display on categories 2022-05-31 23:21:04 +02:00
Ludovic CANDELLIER
5b74c93b2e Works for friday & saturday 2022-05-29 00:46:04 +02:00
Ludovic CANDELLIER
352b109e87 Add new component, add flags on filter 2022-05-09 23:14:50 +02:00
Ludovic CANDELLIER
a70e8c39cf modify filter calculation 2022-05-09 22:33:18 +02:00
Ludovic CANDELLIER
439a339027 fixes on merchandise 2022-05-02 08:34:40 +02:00
Ludovic CANDELLIER
2ee339a022 fix on empty picture 2022-04-25 23:43:25 +02:00
Ludovic CANDELLIER
84063d2f72 Fix on merchandises 2022-04-25 23:31:24 +02:00
Ludovic CANDELLIER
61a52ef330 Fixes on available offers in category childrens for building menu 2022-04-25 22:36:43 +02:00
Ludovic CANDELLIER
c9bf18d87d fix on shelve with available offers 2022-04-25 21:59:53 +02:00
Ludovic CANDELLIER
328d791b87 fix on empty shelve 2022-04-25 20:46:18 +02:00
Ludovic CANDELLIER
e22a541342 fix 2022-04-25 20:29:50 +02:00
Ludovic CANDELLIER
d8bf91da54 Add plus on products 2022-04-25 20:02:28 +02:00
Ludovic CANDELLIER
5747b93952 Add producers 2022-04-25 11:07:02 +02:00
Ludovic CANDELLIER
570374bab7 Add new data in getBasket context 2022-04-24 23:49:28 +02:00
Ludovic CANDELLIER
416c724ad1 Add new data in getBasket context 2022-04-24 23:20:52 +02:00
Ludovic CANDELLIER
5d68e8787a 'fixes' 2022-04-24 22:07:31 +02:00
Ludovic CANDELLIER
e4672a42d7 fixes 2022-04-22 02:32:53 +02:00
Ludovic CANDELLIER
94234218d6 Filters collapsed, customer auth and register, fix on basket recalculation 2022-04-20 00:16:16 +02:00
Ludovic CANDELLIER
a12dd0c653 fix on basket 2022-04-17 00:16:36 +02:00
Ludovic CANDELLIER
fe1e14d2c0 Multi-images component, refactoring medias functions 2022-04-16 19:33:17 +02:00
Ludovic CANDELLIER
1dc815bf39 Add count function for images herited 2022-04-16 13:58:09 +02:00
Ludovic CANDELLIER
2d111605f2 Add management of merchandises, enhance imageable trait 2022-04-16 11:40:19 +02:00
Ludovic CANDELLIER
68a13b7a58 Build form for merchandise 2022-04-14 23:41:58 +02:00
Ludovic CANDELLIER
c2ef0c7b35 Add merchandise, fix articletosell with src for images 2022-04-14 23:20:09 +02:00
Ludovic CANDELLIER
9c2b9cf02e wip 3d 2022-04-13 23:49:48 +02:00
Ludovic CANDELLIER
6e133246cf enhance add to basket 2022-04-01 00:11:15 +02:00
Ludovic CANDELLIER
eff2cb21c7 Add homepage 2022-03-30 22:23:57 +02:00
Ludovic CANDELLIER
c50bd2aead fix 2022-03-30 18:02:19 +02:00
Ludovic CANDELLIER
dd0dddb1ff fix 2022-03-30 18:01:01 +02:00
Ludovic CANDELLIER
34d273e510 Fix slider 2022-03-30 17:54:50 +02:00
Ludovic CANDELLIER
f1f1a8bc70 fix 2022-03-30 16:43:10 +02:00
Ludovic CANDELLIER
c1a0d449be fix 2022-03-30 16:42:46 +02:00
Ludovic CANDELLIER
957d033e2d fix 2022-03-30 16:38:49 +02:00
Ludovic CANDELLIER
dde59a0c90 Fixes on grouping 2022-03-30 16:34:08 +02:00
Ludovic CANDELLIER
1dced19068 Fix 2022-03-30 16:03:26 +02:00
Ludovic CANDELLIER
1c5db3c654 fix 2022-03-30 16:01:12 +02:00
Ludovic CANDELLIER
c8cd3e4fa6 fix 2022-03-30 15:59:31 +02:00
Ludovic CANDELLIER
e312572bcc Fix on tarif with sale_channel 2022-03-30 15:46:10 +02:00
Ludovic CANDELLIER
c4bb4fdd59 Try to fix price_lists by sale_channel 2022-03-30 00:36:58 +02:00
Ludovic CANDELLIER
ff18a0f5bf fix empty 2022-03-24 15:07:55 +01:00
Ludovic CANDELLIER
573e4dc6cb fix empty 2022-03-24 15:06:51 +01:00
Ludovic CANDELLIER
2a98b24bc1 Add calculations on basket 2022-03-24 14:57:39 +01:00
Ludovic CANDELLIER
c357ea932a Add toggle for homepage 2022-03-24 10:08:23 +01:00
Ludovic CANDELLIER
c65056531c Manage homepage by article, modify article template, enhance basket (add selector) 2022-03-24 00:48:26 +01:00
Ludovic CANDELLIER
ddc5f2664c Add variations, slider, fix cart ... 2022-03-21 21:52:12 +01:00
Ludovic CANDELLIER
0eaa11b2a9 Fix on default sale channel 2022-03-07 22:58:40 +01:00
Ludovic CANDELLIER
aa50f908ba inherited description 2022-03-07 22:50:33 +01:00
Ludovic CANDELLIER
719f89cc50 Fix name with spaces 2022-03-07 22:19:57 +01:00
Ludovic CANDELLIER
14931bc5e6 Add method to get image with parent for article 2022-03-07 22:10:59 +01:00
Ludovic CANDELLIER
30876ba67d add constaint on stock 2022-03-07 20:17:50 +01:00
Ludovic CANDELLIER
5794cbb045 Fixes on articles by sale_channel 2022-02-22 23:03:29 +01:00
Ludovic CANDELLIER
27893eaa7e fix empty 2022-02-22 22:46:10 +01:00
Ludovic CANDELLIER
53d1307837 fix 2022-02-22 22:41:50 +01:00
Ludovic CANDELLIER
f8c686caa3 Add prices and filtering by sale_channel with default 2022-02-22 22:32:46 +01:00
Ludovic CANDELLIER
3c3481b39d Add new search of articles 2022-02-21 09:09:36 +01:00
Ludovic CANDELLIER
c75f580ad2 Add price taxed 2022-02-20 21:59:19 +01:00
Ludovic CANDELLIER
ea53cb4c8a change construction of articles/offers 2022-02-20 21:38:21 +01:00
Ludovic CANDELLIER
4b2c431ee9 fix 2022-02-18 09:48:02 +01:00
Ludovic CANDELLIER
655f502279 Fix visible 2022-02-18 09:46:20 +01:00
Ludovic CANDELLIER
6556127cc8 Fix save 2022-02-17 16:27:44 +01:00
Ludovic CANDELLIER
b3a2ad31b2 fix empty 2022-02-17 12:42:46 +01:00
Ludovic CANDELLIER
7d6c7ca36d fix empty 2022-02-17 12:30:17 +01:00
Ludovic CANDELLIER
5701985734 active visible on shelves 2022-02-17 12:27:20 +01:00
Ludovic CANDELLIER
9e064bcd74 fix 2022-02-17 12:14:22 +01:00
Ludovic CANDELLIER
f0386269e6 fix on new tariff_unities 2022-02-17 12:03:01 +01:00
Ludovic CANDELLIER
90d683f7ed Fix old methods 2022-02-17 11:52:19 +01:00
Ludovic CANDELLIER
7723b475ac change old route 2022-02-17 11:44:47 +01:00
Ludovic CANDELLIER
d8ce8f5259 Fix save 2022-02-17 11:42:11 +01:00
Ludovic CANDELLIER
48359525bf Upgrade package category and dependencies for php8.0 2022-02-17 11:38:19 +01:00
Ludovic CANDELLIER
0399d90ca7 fix unsetted 2022-02-17 09:44:30 +01:00
Ludovic CANDELLIER
32362d74dc Fix 2022-02-16 09:42:08 +01:00
Ludovic CANDELLIER
b90d3ba3f2 Fix categories & varieties 2022-02-16 09:17:51 +01:00
Ludovic CANDELLIER
642fd52d36 fix name 2022-02-15 13:55:28 +01:00
Ludovic CANDELLIER
a6a4b9e59a change template 2022-02-15 13:52:03 +01:00
Ludovic CANDELLIER
3c00452219 Fix 2022-02-15 13:19:30 +01:00
Ludovic CANDELLIER
2e14e494a1 fix search 2022-02-09 09:25:48 +01:00
Ludovic CANDELLIER
b4856266c8 Add method to get offers by articles with siblings, enhance display 2022-01-30 22:48:04 +01:00
Ludovic CANDELLIER
5e5f12ddb2 Fix bug on select2 in modal filters, add filters by tags and shelves on articles 2022-01-30 15:04:08 +01:00
Ludovic CANDELLIER
5799eb36fc fix roles 2022-01-30 00:30:21 +01:00
Ludovic CANDELLIER
b4057c28d0 fix 2022-01-25 23:32:39 +01:00
Ludovic CANDELLIER
45c7385046 fix on image 2022-01-25 23:27:55 +01:00
Ludovic CANDELLIER
fb047aa036 Add no visual 2022-01-25 22:59:17 +01:00
Ludovic CANDELLIER
ed1d87a7d1 Fixes on tag_Groups and variations, add migrations 2022-01-25 22:25:18 +01:00
Ludovic CANDELLIER
9b6bac5545 fix on empty articles 2022-01-24 00:47:45 +01:00
Ludovic CANDELLIER
1fb9319bac Fix on home 2022-01-24 00:32:46 +01:00
Ludovic CANDELLIER
30666e2931 better integration of filters 2022-01-24 00:31:23 +01:00
Ludovic CANDELLIER
3e26bf368b Add toggle by rows/by cards 2022-01-23 23:16:56 +01:00
Ludovic CANDELLIER
52019357ba Add display of shelve 2022-01-23 22:49:23 +01:00
Ludovic CANDELLIER
81b6c87d59 fixes 2022-01-23 21:48:37 +01:00
Ludovic CANDELLIER
fe759565a8 Add filter by sale_channel, add method to get prices by offer, sale_channel and quantity 2022-01-23 21:37:54 +01:00
Ludovic CANDELLIER
94a162deb7 fix 2022-01-23 09:02:43 +01:00
Ludovic CANDELLIER
f4aecc9130 fixes on tags with slug 2022-01-22 22:05:18 +01:00
Ludovic CANDELLIER
f35650b234 Add tariff unities management 2022-01-22 19:26:35 +01:00
Ludovic CANDELLIER
6f04a8e7b7 Fixes size of description on article, fix save form on Families and genres 2022-01-22 17:53:24 +01:00
Ludovic CANDELLIER
2912dc6794 Display filters, and fix css for article 2022-01-22 13:12:43 +01:00
Ludovic CANDELLIER
6ff65eb927 fix 2022-01-19 22:43:32 +01:00
Ludovic CANDELLIER
7ae2c4b07c Add parameters to display by rows 2022-01-18 23:46:06 +01:00
Ludovic CANDELLIER
cefe956bc4 Add display articles by rows, and display article in full mode 2022-01-18 23:39:27 +01:00
Ludovic CANDELLIER
3641bd7d68 Fix on data for article, problem with id on polymorphic 2022-01-18 00:08:04 +01:00
Ludovic CANDELLIER
050fd76122 Add deep relations 2022-01-14 00:03:21 +01:00
Ludovic CANDELLIER
95ca3c6404 invert query from offers->articles to articles->offers 2022-01-05 22:05:30 +01:00
Ludovic CANDELLIER
a3c6fc6ebe [WIP] Add thumb on offers, refactor categories, try to fix counter on relations polymorphic with eage loader, bad pattern ! 2021-12-17 00:30:07 +01:00
Ludovic CANDELLIER
2be07ce72c fixes 2021-11-24 23:04:13 +01:00
Ludovic CANDELLIER
2f3da7d700 Manage address and deliveries 2021-11-24 20:57:12 +01:00
Ludovic CANDELLIER
b0b1164881 Fix 2021-11-24 16:15:56 +01:00
Ludovic CANDELLIER
6439d2d4ad fix on server 2021-11-24 15:56:02 +01:00
Ludovic CANDELLIER
6f0506a71e Fix on php 7.4 2021-11-24 15:53:17 +01:00
Ludovic CANDELLIER
5b84ff74e3 Fixes for deliveries vs sale_channels 2021-11-23 23:37:47 +01:00
Ludovic CANDELLIER
323330b1a1 fixes 2021-11-07 23:41:17 +01:00
Ludovic CANDELLIER
63c6671c97 fixes 2021-11-07 19:58:38 +01:00
Ludovic CANDELLIER
c7c8e18cbc Fixes 2021-11-07 17:16:35 +01:00
Ludovic CANDELLIER
46b751c361 change last_nulls on mysql , is compatible with mariadb ? 2021-11-04 16:59:11 +01:00
Ludovic CANDELLIER
4761656405 Fix 2021-11-04 16:37:42 +01:00
Ludovic CANDELLIER
eb0c9444bc Add filters 2021-11-01 23:42:53 +01:00
Ludovic CANDELLIER
e8d503b65d Minor fixes on traits 2021-11-01 18:50:17 +01:00
Ludovic CANDELLIER
900da34b57 Add thumbs views in datatables with traits 2021-11-01 18:37:25 +01:00
Ludovic CANDELLIER
ae20643879 add offers count, & minor fixes code standards 2021-11-01 16:26:31 +01:00
Ludovic CANDELLIER
8aaab4345f Minor fixes, coding standards 2021-11-01 00:50:10 +01:00
Ludovic CANDELLIER
e356b3fcda Refactoring, change menu, add many features 2021-10-30 02:22:51 +02:00
Ludovic CANDELLIER
fae7b7897f Fix 2021-10-26 21:51:47 +02:00
Ludovic CANDELLIER
e040837ce6 Fix 2021-10-26 21:47:00 +02:00
Ludovic CANDELLIER
86f6ee9a13 Synchro back-office, fix on tariffs 2021-10-26 21:41:46 +02:00
Ludovic CANDELLIER
c150be2c3e refactor, better class namespace intergration 2021-10-04 14:09:51 +02:00
Ludovic CANDELLIER
a7f661ab10 fixes 2021-10-04 13:49:45 +02:00
Ludovic CANDELLIER
9d21f28d9e downgrade for incompatibility with depedencies 2021-09-22 22:15:38 +02:00
Ludovic CANDELLIER
95997a4a0a Saving for offers 2021-09-22 22:14:39 +02:00
Ludovic CANDELLIER
2195ca122c fix 2021-09-22 21:03:42 +02:00
Ludovic CANDELLIER
85465f67c6 Fix tags with group name 2021-09-22 21:03:19 +02:00
Ludovic CANDELLIER
c347b7fe82 Remove duplicate migrations 2021-09-22 21:02:45 +02:00
Ludovic CANDELLIER
e98266e556 Upgrade boilerplate 2021-09-22 21:01:43 +02:00
Ludovic CANDELLIER
ffb9f81353 Add relations in tables, add saving states for datatables, minor fixes 2021-09-14 23:14:03 +02:00
Ludovic CANDELLIER
1dcc3e34a9 [WIP] Tentative d'ajout des tarifs dans le tableau 2021-09-09 00:30:36 +02:00
Ludovic CANDELLIER
9cf96b7d4e fixes 2021-09-09 00:03:24 +02:00
Ludovic CANDELLIER
50d445bb3b fix filter on prices by tariff 2021-09-01 10:25:59 +02:00
Ludovic CANDELLIER
b20c32d722 add description on variations 2021-09-01 09:10:58 +02:00
Ludovic CANDELLIER
4614ea57cf fix adding price for count(prices) > 3 2021-08-31 23:33:10 +02:00
Ludovic CANDELLIER
73cfe5a42e Fix on refreshing description & images from products 2021-08-31 23:12:18 +02:00
Ludovic CANDELLIER
144532acbf Add refreshing for inherited data 2021-08-30 22:59:50 +02:00
Ludovic CANDELLIER
8d3ccbf148 Enhance categories, add tags, parent 2021-08-26 17:45:37 +02:00
Ludovic CANDELLIER
e407934e2a Fix menu catalogue, fix path for price modal 2021-08-26 15:59:56 +02:00
Ludovic CANDELLIER
5ddcebc303 Fix variation 2021-08-26 15:53:41 +02:00
Ludovic CANDELLIER
04685cc7dc Fix translation yet forced on tags 2021-08-26 13:42:58 +02:00
Ludovic CANDELLIER
32c532d49b Fixes on tag updating 2021-08-26 09:54:37 +02:00
Ludovic CANDELLIER
67f490b2fe Fixes 2021-08-24 23:41:10 +02:00
Ludovic CANDELLIER
82afe63c60 Fix translation 2021-08-24 22:50:18 +02:00
Ludovic CANDELLIER
a84955412a Fix translations typo case 2021-08-24 22:40:22 +02:00
Ludovic CANDELLIER
f32ac13f1e Fix tree 2021-08-24 22:26:37 +02:00
Ludovic CANDELLIER
8d51ced269 Fix relationship 2021-08-24 20:42:51 +02:00
Ludovic CANDELLIER
967af93f8c fix segregation of article 2021-08-24 20:28:57 +02:00
Ludovic CANDELLIER
c3f66af009 Fix admin path 2021-08-24 19:14:29 +02:00
Ludovic CANDELLIER
46316ac974 Fix css 2021-08-24 19:03:52 +02:00
Ludovic CANDELLIER
7d1b2f1273 Fix package eloquent-macro unavailable for now 2021-08-24 16:40:31 +02:00
Ludovic CANDELLIER
5d99f9a09a remove old files 2021-08-24 16:27:05 +02:00
Ludovic CANDELLIER
24fffce7a1 Fix on preview mode 2021-08-23 23:56:46 +02:00
Ludovic CANDELLIER
81fbec892c comments 2021-08-21 19:48:21 +02:00
Ludovic CANDELLIER
9a0601d473 Rename Admin views directory, add some functions on models 2021-07-27 22:12:58 +02:00
Ludovic CANDELLIER
daeece59c9 Fixes on adding price & errors 2021-07-27 17:33:18 +02:00
Ludovic CANDELLIER
b879f11c99 Add new version in repository 2021-07-25 23:19:27 +02:00
Ludovic CANDELLIER
f75632b054 MCD 2021-06-05 18:00:10 +02:00
Ludovic CANDELLIER
b50f50ea62 [WIP] Refactor project 2021-05-21 00:21:05 +02:00
Ludovic CANDELLIER
4ce0fa942d v1 2021-05-07 00:14:27 +02:00
Ludovic CANDELLIER
fd2e87aa07 Fixes 2021-04-19 21:45:17 +02:00
Ludovic CANDELLIER
ebea9844dd Fixes on article preview 2021-04-16 00:04:00 +02:00
Ludovic CANDELLIER
096351ae4e Fixes on widget uploder 2021-04-15 23:58:51 +02:00
Ludovic CANDELLIER
f5ca57fdf2 Add preview from father, add new features 2021-04-11 00:36:41 +02:00
Ludovic CANDELLIER
f781158e36 Fix typo 2021-04-08 16:59:06 +02:00
Ludovic CANDELLIER
7a9f20acb9 Fix Typo 2021-04-08 16:57:50 +02:00
Ludovic CANDELLIER
098a46f3a0 Fix 2021-04-08 16:18:21 +02:00
52 changed files with 1436 additions and 2373 deletions

74
AGENTS.md Normal file
View File

@@ -0,0 +1,74 @@
# Repository Guidelines
## Project Structure & Module Organization
OpenSem builds on Laravel 9.
Core application code lives in `app/`, while HTTP routes reside in
`routes/` and Blade views in `resources/views/`. Reusable front-end
assets (JS, SCSS, images) sit under `resources/` and are compiled into
`public/` via Laravel Mix.
Database blueprints are versioned in `database/migrations/` with seeds
in `database/seeders/`.
Tests are organised in `tests/Unit/` and `tests/Feature/`; keep large
fixtures in `tests/Fixtures/` to avoid polluting source directories.
## Build, Test, and Development Commands
- `composer install` — install PHP dependencies defined in
`composer.json`.
- `php artisan serve` — start a local HTTP server on port 8000.
- `npm install && npm run dev` — install Node tooling and build UI
assets for development.
- `npm run prod` — generate minified production assets in `public/`.
- `php artisan migrate --seed` — apply database schema and load
default data for demo instances.
- `./build.sh` — builds a `.tar.xz` that contains the production and
deployement ready source to be deployed.
## Coding Style & Naming Conventions
Follow PSR-12 with four-space indentation and `snake_case` database
columns. Controllers, models, and Livewire components use StudlyCase
class names; private methods remain `camelCase`. Run `composer run
inspect` before opening a PR to execute `phpcs` and `phpstan`. For
front-end changes, keep Blade sections in lowercase kebab IDs (for
example, `@section('order-summary')`).
## Testing Guidelines
Use PHPUnit via `php artisan test`; target deterministic tests with
clear Arrange/Act/Assert blocks. Feature tests should mirror top-level
route names (e.g., `OrdersTest.php`). Unit tests belong in
`tests/Unit/` and should stub external services. When adding
migrations or service integrations, include coverage that exercises
failure paths. For granular checks, `./vendor/bin/phpunit --filter
FooTest` is acceptable, but always run the full suite before pushing.
## Commit & Pull Request Guidelines
Commits in this repo mix Conventional Commit prefixes (`new:`, `fix:`,
`chg:`); `fix: prevent null totals`. Keep messages in the imperative
mood and reference ticket IDs when available.
Pull requests must describe scope, list schema or configuration
changes, and note any manual follow-up (cron, storage links,
queues).
Attach screenshots or terminal logs when touching UI or console
output, and ensure CI scripts (when available) pass.
## Environment & Security Notes
Copy `.env.example` to `.env` and run `php artisan key:generate`
before local work. Never commit `.env`, `storage/`, or database dumps
containing sensitive data. Use the Docker resources in `docker/` only
for reproducible environments; keep secrets in your host overrides,
not in version control.

View File

@@ -43,6 +43,7 @@ COPY . /app
WORKDIR /app
RUN mkdir -p /app/bootstrap/cache \
/app/storage/media-library/temp \
/app/storage/framework/cache \
/app/storage/framework/views \
/app/storage/framework/sessions \
@@ -84,8 +85,6 @@ RUN apk add --no-cache xz
# bring PHP app with vendor
COPY --from=phpdeps /app /app
# ensure required runtime dirs exist (empty is fine)
RUN mkdir -p storage/framework/{cache,views,sessions} bootstrap/cache
# create artifact (use tar + xz so we don't depend on GNU tar -J)
RUN mkdir -p /out \
&& tar -C /app -cf /out/app.tar \

View File

@@ -1,10 +1,19 @@
## A propos de OpenSem
OpenSem est une solution de commerce électronique et un ERP développé pour les besoins exprimés.
OpenSem est une solution de commerce électronique et un ERP développé
pour les besoins exprimés.
Développée par Ludovic Candellier en étroite relation avec
Jardin'Envie.
L'application est écrite en PHP et est basée sur Laravel.
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
Laravel is a web application framework with expressive, elegant
syntax. We believe development must be an enjoyable and creative
experience to be truly fulfilling. Laravel takes the pain out of
development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
@@ -14,12 +23,21 @@ Laravel is a web application framework with expressive, elegant syntax. We belie
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
Laravel is accessible, powerful, and provides tools required for
large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
Laravel has the most extensive and thorough
[documentation](https://laravel.com/docs) and video tutorial library
of all modern web application frameworks, making it a breeze to get
started with the framework.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 1500 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
If you don't feel like reading, [Laracasts](https://laracasts.com) can
help. Laracasts contains over 1500 video tutorials on a range of
topics including Laravel, modern PHP, unit testing, and
JavaScript. Boost your skills by digging into our comprehensive video
library.
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
The Laravel framework is open-sourced software licensed under the [MIT
license](https://opensource.org/licenses/MIT).

View File

@@ -11,7 +11,7 @@ use Yajra\DataTables\Html\Column;
class CustomerInvoicesDataTable extends DataTable
{
public $model_name = 'invoices';
public $model_name = 'customer_invoices';
public $sortedColumn = 1;

View File

@@ -3,7 +3,6 @@
namespace App\Http\Controllers\Admin\Botanic;
use App\Datatables\Botanic\SpeciesDataTable;
use App\Repositories\Botanic\Genres;
use App\Repositories\Botanic\Species;
use Illuminate\Http\Request;
@@ -21,7 +20,7 @@ class SpecieController extends Controller
public function create()
{
$data = Genres::init();
$data = Species::init();
return view('Admin.Botanic.Species.create', $data);
}
@@ -36,7 +35,7 @@ class SpecieController extends Controller
public function edit($id)
{
$data = Genres::init();
$data = Species::init();
$data['specie'] = Species::getFull($id);
return view('Admin.Botanic.Species.edit', $data);

View File

@@ -12,15 +12,6 @@ class CustomerInvoiceController extends Controller
return $dataTable->render('Admin.Shop.CustomerInvoices.list');
}
public function show($id)
{
$data = [
'invoice' => Invoices::get($id),
];
return view('Admin.Shop.CustomerInvoices.view', $data);
}
public function destroy($id)
{
return Invoices::destroy($id);

View File

@@ -71,7 +71,10 @@ class PriceListValueController extends Controller
public function addPrice($index)
{
$data['index'] = $index;
$data = [
'index' => $index,
'taxes' => Taxes::getOptions(),
];
return view('Admin.Shop.PriceListValues.partials.row_price', $data);
}

View File

@@ -2,9 +2,13 @@
namespace App\Http\Controllers\Shop;
use App\Repositories\Core\User\ShopCart;
use App\Repositories\Shop\Baskets;
use App\Repositories\Shop\CustomerAddresses;
use App\Repositories\Shop\Customers;
use App\Repositories\Shop\Offers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Validator;
class CustomerController extends Controller
@@ -44,8 +48,69 @@ class CustomerController extends Controller
public function storeProfileAjax(Request $request)
{
$data = $request->all();
if (array_key_exists('default_sale_channel_id', $data)) {
$customer = Customers::get(Customers::getId());
$newSaleChannelId = (int) $data['default_sale_channel_id'];
$currentSaleChannelId = (int) ($customer->default_sale_channel_id ?? 0);
if ($newSaleChannelId && $newSaleChannelId !== $currentSaleChannelId && ShopCart::count() > 0) {
$cartItems = ShopCart::getContent();
$unavailable = [];
foreach ($cartItems as $item) {
$offerId = (int) $item->id;
if (! Offers::getPrice($offerId, 1, $newSaleChannelId)) {
$offer = Offers::get($offerId, ['article']);
$unavailable[] = $offer->article->name ?? $item->name;
if (count($unavailable) >= 3) {
break;
}
}
}
if (! empty($unavailable)) {
$list = implode(', ', $unavailable);
return response()->json([
'error' => 1,
'message' => __('Certains articles de votre panier ne sont pas disponibles dans ce canal : :products. Merci de finaliser votre commande ou de retirer ces articles avant de changer de canal.', ['products' => $list]),
], 422);
}
}
}
$customerId = $data['id'] ?? Customers::getId();
$requestedDefaultSaleChannelId = $data['default_sale_channel_id'] ?? null;
$hasDefaultSaleChannelColumn = Schema::hasColumn('shop_customers', 'default_sale_channel_id');
if (! $hasDefaultSaleChannelColumn) {
unset($data['default_sale_channel_id']);
}
$customer = Customers::store($data);
if (! $customer) {
return response()->json([
'error' => 1,
'message' => __('Impossible de mettre à jour votre profil pour le moment.'),
], 422);
}
if ($hasDefaultSaleChannelColumn && $requestedDefaultSaleChannelId !== null) {
Customers::setDefaultSaleChannel($customerId, $requestedDefaultSaleChannelId);
}
$freshCustomer = Customers::get($customerId, ['sale_channels']);
Customers::guard()->setUser($freshCustomer);
if ($requestedDefaultSaleChannelId !== null) {
session(['shop.default_sale_channel_id' => $requestedDefaultSaleChannelId]);
Baskets::refreshPrices((int) $requestedDefaultSaleChannelId);
}
return response()->json(['error' => 0]);
}

View File

@@ -15,8 +15,14 @@ class InvoiceController extends Controller
public function view($uuid)
{
$invoice = Invoices::view($uuid);
if (! $invoice) {
abort(404);
}
$data = [
'invoice' => Invoices::view($uuid),
'invoice' => $invoice,
];
return view('Shop.Invoices.view', $data);

View File

@@ -49,12 +49,22 @@ class OrderController extends Controller
{
if (ShopCart::count()) {
$customer = Customers::getWithAddresses();
$deliveries = Deliveries::getByCustomer();
$customerId = $customer ? $customer->id : false;
$defaultSaleChannelId = SaleChannels::getDefaultID($customerId);
$deliveries = $defaultSaleChannelId
? Deliveries::getBySaleChannels([$defaultSaleChannelId])
: Deliveries::getByCustomer($customerId);
$deliveries = $deliveries ? $deliveries->values() : collect();
$customerData = $customer ? $customer->toArray() : false;
if ($customerData && $defaultSaleChannelId) {
$customerData['default_sale_channel_id'] = $defaultSaleChannelId;
}
$data = [
'customer' => $customer ? $customer->toArray() : false,
'customer' => $customerData,
'basket' => Baskets::getBasketTotal(),
'deliveries' => $deliveries ? $deliveries->toArray() : [],
'deliveries' => $deliveries->toArray(),
'delivery_types' => DeliveryTypes::getWithPrice(Baskets::getWeight()),
];

View File

@@ -3,6 +3,7 @@
namespace App\Http\Requests\Admin\Shop;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StoreArticlePost extends FormRequest
{
@@ -13,8 +14,13 @@ class StoreArticlePost extends FormRequest
public function rules()
{
$articleId = $this->input('id');
return [
'ref' => 'required|unique:shop_articles',
'ref' => [
'required',
Rule::unique('shop_articles', 'ref')->ignore($articleId)->whereNull('deleted_at'),
],
'product_type' => 'required',
'product_id' => 'required',
'article_nature_id' => 'required',

View File

@@ -11,9 +11,6 @@ class AppServiceProvider extends ServiceProvider
{
public function boot()
{
if (config('app.env') === 'production') {
\URL::forceScheme('https');
}
Schema::defaultStringLength(191);
View::composer('Shop.layout.layout', LayoutComposer::class);
}

View File

@@ -65,6 +65,9 @@ class Varieties
{
$images = $data['images'] ?? false;
$tags = $data['tags'] ?? false;
if (! array_key_exists('plus', $data) || $data['plus'] === null) {
$data['plus'] = '';
}
unset($data['images']);
unset($data['tags']);
$variety = self::store($data);

View File

@@ -2,6 +2,8 @@
namespace App\Repositories\Core;
use Spatie\MediaLibrary\MediaCollections\Models\Media as MediaModel;
class Medias
{
public static function getImage($model, $conversion = 'normal', $collection = 'images')
@@ -79,13 +81,9 @@ class Medias
public static function getImageSrc($image)
{
if (! $image) {
return null;
}
$id = $image['id'];
$filename = self::getFilename($image);
$media = self::resolveMedia($image);
return "/storage/{$id}/{$filename}";
return $media ? $media->getUrl() : null;
}
public static function getThumbSrc($image)
@@ -110,6 +108,12 @@ class Medias
public static function getSrcByType($image, $type)
{
$media = self::resolveMedia($image);
if ($media) {
return $type ? $media->getUrl($type) : $media->getUrl();
}
return $image ? '/storage/'.$image['id'].'/conversions/'.self::getFilename($image, $type) : false;
}
@@ -124,4 +128,48 @@ class Medias
{
return str_replace(['#', '/', '\\', ' '], '-', $name);
}
protected static function resolveMedia($image): ?MediaModel
{
if ($image instanceof MediaModel) {
return $image;
}
if (is_null($image)) {
return null;
}
if (is_array($image)) {
return self::hydrateMedia($image);
}
if (is_object($image)) {
if ($image instanceof \ArrayAccess) {
return self::hydrateMedia((array) $image);
}
$array = method_exists($image, 'toArray') ? $image->toArray() : (array) $image;
return self::hydrateMedia($array);
}
$id = data_get($image, 'id');
return $id ? MediaModel::query()->withoutGlobalScopes()->find($id) : null;
}
protected static function hydrateMedia(array $attributes): ?MediaModel
{
$id = data_get($attributes, 'id');
if (! $id) {
return null;
}
$media = new MediaModel();
$media->forceFill($attributes);
$media->exists = true;
return $media;
}
}

View File

@@ -27,21 +27,34 @@ class ArticleTags
switch ($article->product_type) {
case 'App\Models\Botanic\Variety':
$data += $article->product->tags->toArray();
if ($article->product->specie ?? false) {
$data += $article->product->specie->tags->toArray();
$variety = $article->product;
if ($variety && $variety->tags) {
$data = array_merge($data, $variety->tags->toArray());
}
if ($variety && $variety->specie && $variety->specie->tags) {
$data = array_merge($data, $variety->specie->tags->toArray());
}
break;
case 'App\Models\Botanic\Specie':
$data += $article->product->tags->toArray();
$specie = $article->product;
if ($specie && $specie->tags) {
$data = array_merge($data, $specie->tags->toArray());
}
break;
case 'App\Models\Shop\Merchandise':
$data += $article->product->tags->toArray();
$data += $article->product->producer->tags->toArray();
$merchandise = $article->product;
if ($merchandise && $merchandise->tags) {
$data = array_merge($data, $merchandise->tags->toArray());
}
if ($merchandise && $merchandise->producer && $merchandise->producer->tags) {
$data = array_merge($data, $merchandise->producer->tags->toArray());
}
break;
default:
}
$data += $article->tags->toArray();
if ($article->tags) {
$data = array_merge($data, $article->tags->toArray());
}
foreach ($data as $tag) {
if (! isset($tags[$tag['group']][$tag['name']])) {

View File

@@ -5,6 +5,9 @@ namespace App\Repositories\Shop;
use App\Models\Shop\Article;
use App\Repositories\Botanic\Species;
use App\Repositories\Botanic\Varieties;
use App\Repositories\Shop\SaleChannels;
use App\Repositories\Shop\Customers;
use Illuminate\Support\Facades\Schema;
use App\Repositories\Core\Comments;
use App\Traits\Model\Basic;
use App\Traits\Repository\Imageable;
@@ -16,7 +19,7 @@ class Articles
public static function autocomplete($str)
{
$data = Article::byAutocomplete($str)->orderBy('name')->limit(20)->pluck('name', 'id');
$data = Article::byAutocomplete($str)->visible()->orderBy('name')->limit(20)->pluck('name', 'id');
$export = [];
foreach ($data as $key => $name) {
$export[] = ['value' => $key, 'text' => $name];
@@ -70,9 +73,33 @@ class Articles
public static function getArticleToSell($id, $saleChannelId = false)
{
$saleChannelId = $saleChannelId ?: SaleChannels::getDefaultID();
$sessionSaleChannelId = session('shop.default_sale_channel_id');
$customer = Customers::getAuth();
$hasDefaultSaleChannelColumn = Schema::hasColumn('shop_customers', 'default_sale_channel_id');
$customerDefaultSaleChannelId = ($customer && $hasDefaultSaleChannelColumn)
? $customer->default_sale_channel_id
: null;
$customerSaleChannelIds = [];
if ($customer) {
$customer->loadMissing('sale_channels:id');
$customerSaleChannelIds = $customer->sale_channels->pluck('id')->toArray();
}
$data = self::getArticle($id);
$data['offers'] = self::getOffersGroupedByNature($id, $saleChannelId);
$currentSaleChannel = $saleChannelId ? SaleChannels::get($saleChannelId) : null;
$data['current_sale_channel'] = $currentSaleChannel ? $currentSaleChannel->toArray() : null;
$data['available_sale_channels'] = Offers::getSaleChannelsForArticle($id);
$data['debug_sale_channel'] = [
'session_default_sale_channel_id' => $sessionSaleChannelId,
'customer_default_sale_channel_id' => $customerDefaultSaleChannelId,
'customer_linked_sale_channel_ids' => $customerSaleChannelIds,
'resolved_sale_channel_id' => $saleChannelId,
'has_default_sale_channel_column' => $hasDefaultSaleChannelColumn,
];
return $data;
}
@@ -108,8 +135,11 @@ class Articles
$data['specie'] = $article->product ? $article->product->description : '';
break;
case 'App\Models\Shop\Merchandise':
$data['merchandise'] = $article->product ? $article->product->description : '';
$data['producer'] = $article->product->producer->description;
$merchandise = $article->product;
$data['merchandise'] = $merchandise ? ($merchandise->description ?? '') : '';
if ($merchandise && $merchandise->producer) {
$data['producer'] = $merchandise->producer->description ?? '';
}
break;
default:
}

View File

@@ -100,12 +100,19 @@ class Baskets
$offers = Offers::getWithPricesByIds(self::getIds(), $saleChannelId);
foreach ($basket as $item) {
$offer = $offers->where('id', $item->id)->first();
if (! $offer) {
continue;
}
$priceValue = Offers::getPrice($item->id, $item->quantity, $saleChannelId);
$unitPrice = $priceValue ? (float) $priceValue->price_taxed : (float) $item->price;
$article_nature = strtolower($offer->article->article_nature->name);
$data[$article_nature][] = [
'id' => (int) $item->id,
'name' => $item->name,
'quantity' => (int) $item->quantity,
'price' => $item->price,
'price' => $unitPrice,
'variation' => $offer->variation->name,
'image' => Articles::getPreviewSrc(ArticleImages::getFullImageByArticle($offer->article)),
'latin' => $offer->article->product->specie->latin ?? false,
@@ -115,6 +122,24 @@ class Baskets
return $data ?? false;
}
public static function refreshPrices($saleChannelId = false)
{
$saleChannelId = $saleChannelId ? $saleChannelId : SaleChannels::getDefaultID();
$basket = ShopCart::getContent();
foreach ($basket as $item) {
$priceValue = Offers::getPrice($item->id, $item->quantity, $saleChannelId);
if (! $priceValue) {
continue;
}
ShopCart::get()->update($item->id, [
'price' => $priceValue->price_taxed,
]);
}
}
public static function getBasketData($id, $quantity = 1)
{
$offer = Offers::get($id, ['article', 'variation']);

View File

@@ -31,16 +31,31 @@ class Customers
public static function getSaleChannels($customerId = false)
{
$customer = $customerId ? self::get($customerId) : self::getAuth();
$saleChannels = $customer ? $customer->sale_channels : false;
$saleChannels = collect();
return $saleChannels ? $saleChannels : SaleChannels::getDefault();
if ($customer) {
$customer->loadMissing('sale_channels');
$saleChannels = $customer->sale_channels ?? collect();
if ($saleChannels instanceof \Illuminate\Support\Collection && $saleChannels->isNotEmpty()) {
return $saleChannels;
}
}
$default = SaleChannels::getDefault($customerId);
return $default ? collect([$default]) : collect();
}
public static function getSaleChannel($customerId = false)
{
$saleChannels = self::getSaleChannels($customerId);
return $saleChannels->first();
if ($saleChannels instanceof \Illuminate\Support\Collection) {
return $saleChannels->first();
}
return $saleChannels;
}
public static function getDeliveries()
@@ -58,12 +73,22 @@ class Customers
public static function editProfile($id = false)
{
return $id ? [
'customer' => self::get($id, ['addresses', 'deliveries'])->toArray(),
'deliveries' => Deliveries::getAllWithSaleChannel()->toArray(),
if (! $id) {
abort('403');
}
$customer = self::get($id, ['addresses', 'deliveries', 'sale_channels']);
$saleChannels = self::getSaleChannels($id);
return [
'customer' => $customer->toArray(),
'sale_channels' => $saleChannels->toArray(),
'deliveries' => Deliveries::getByCustomer($id)->toArray(),
'sale_channel_checks' => Shop::getSaleChannelAvailabilitySummary($saleChannels->pluck('id')->toArray()),
'orders' => (new CustomerOrdersDataTable())->html(),
'invoices' => (new CustomerInvoicesDataTable())->html(),
] : abort('403');
];
}
public static function getAddresses($id = false)
@@ -154,6 +179,24 @@ class Customers
return $customer->sale_channels()->sync($saleChannels);
}
public static function setDefaultSaleChannel($customerId, $saleChannelId)
{
if (! $customerId) {
return false;
}
$customer = self::get($customerId);
if (! $customer) {
return false;
}
$customer->default_sale_channel_id = $saleChannelId ?: null;
$customer->save();
return $customer->fresh(['sale_channels']);
}
public static function create($data)
{
$data['uuid'] = Str::uuid();

View File

@@ -21,12 +21,12 @@ class Deliveries
$customer = $customerId ? Customers::get($customerId) : Customers::getAuth();
$saleChannels = $customer ? $customer->sale_channels->pluck('id')->toArray() : [SaleChannels::getDefaultID()];
return $saleChannels ? self::getBySaleChannels($saleChannels) : false;
return $saleChannels ? self::getBySaleChannels($saleChannels) : collect();
}
public static function getBySaleChannels($saleChannels)
{
return Delivery::bySaleChannels($saleChannels)->with('sale_channel')->get();
return Delivery::bySaleChannels($saleChannels)->active()->with('sale_channel')->get();
}
public static function getSaleChannelId($deliveryId)
@@ -41,7 +41,7 @@ class Deliveries
public static function getAllWithSaleChannel()
{
return Delivery::orderBy('name', 'asc')->active()->public()->with('sale_channel')->get();
return Delivery::orderBy('name', 'asc')->active()->with('sale_channel')->get();
}
public static function toggleActive($id, $active)

View File

@@ -15,9 +15,15 @@ class DeliveryTypes
$types = self::getAll();
foreach ($types as $type) {
$price = self::getPrice($type->id, $weight);
if ($price === false) {
continue;
}
$data[$type->id] = [
'name' => $type->name,
'price' => self::getPrice($type->id, $weight),
'price' => $price,
];
}

View File

@@ -36,7 +36,13 @@ class Invoices
public static function view($uuid)
{
$data = self::getFullByUUID($uuid)->toArray();
$invoice = self::getFullByUUID($uuid);
if (! $invoice) {
return false;
}
$data = $invoice->toArray();
$data['payment_type'] = InvoicePayments::getPaymentType($data['payment_type']);
$data['status'] = self::getStatus($data['status']);

View File

@@ -3,6 +3,9 @@
namespace App\Repositories\Shop;
use App\Models\Shop\Offer;
use App\Models\Shop\PriceList;
use App\Models\Shop\PriceListValue;
use App\Models\Shop\SaleChannel;
use App\Traits\Model\Basic;
class Offers
@@ -166,4 +169,87 @@ class Offers
{
return Offer::query();
}
public static function getSaleChannelsForArticle($articleId)
{
$channels = SaleChannel::query()
->whereHas('price_lists', function ($query) use ($articleId) {
$query->whereHas('tariff.offers', function ($subQuery) use ($articleId) {
$subQuery->byArticle($articleId);
})->whereHas('price_list_values');
})
->orderBy('name')
->get();
$offers = Offer::query()
->byArticle($articleId)
->with([
'article',
'tariff:id,status_id',
'variation',
])
->get();
return $channels->map(function ($channel) use ($offers) {
$priceValue = null;
$candidateOffer = null;
$allOffersForChannel = [];
foreach ($offers as $offer) {
$priceCandidate = self::getPrice($offer->id, 1, $channel->id);
if ($priceCandidate && (float) $priceCandidate->price_taxed > 0) {
// Get price list name
$priceListName = null;
if ($priceCandidate) {
$priceListModel = PriceList::find($priceCandidate->price_list_id);
$priceListName = $priceListModel ? $priceListModel->name : null;
}
// Collect all offers with their details
$allOffersForChannel[] = [
'id' => $offer->id,
'variation_name' => $offer->variation ? $offer->variation->name : null,
'stock_current' => (int) $offer->stock_current,
'status_id' => (int) $offer->status_id,
'is_active' => (int) $offer->status_id === 1,
'tariff_id' => $offer->tariff_id ? (int) $offer->tariff_id : null,
'price_taxed' => (float) $priceCandidate->price_taxed,
'quantity' => (int) $priceCandidate->quantity,
'price_list_name' => $priceListName,
];
// Keep first valid offer as the main candidate
if (!$candidateOffer) {
$priceValue = $priceCandidate;
$candidateOffer = $offer;
}
}
}
$offerId = $candidateOffer ? $candidateOffer->id : null;
$offerStock = $candidateOffer ? (int) $candidateOffer->stock_current : null;
$offerIsActive = $candidateOffer ? (int) $candidateOffer->status_id === 1 : false;
$offerTariffStatus = $candidateOffer && $candidateOffer->tariff ? (int) $candidateOffer->tariff->status_id : null;
$offerHasStock = $candidateOffer && $candidateOffer->stock_current !== null
? (float) $candidateOffer->stock_current > 0
: null;
$offerTariffId = $candidateOffer && $candidateOffer->tariff_id ? (int) $candidateOffer->tariff_id : null;
return [
'id' => $channel->id,
'name' => $channel->name,
'code' => $channel->code,
'price_taxed' => $priceValue ? (float) $priceValue->price_taxed : null,
'quantity' => $priceValue ? (int) $priceValue->quantity : null,
'offer_id' => $offerId,
'offer_is_active' => $offerIsActive,
'offer_stock_current' => $offerStock,
'offer_has_stock' => $offerHasStock,
'tariff_status_id' => $offerTariffStatus,
'tariff_id' => $offerTariffId,
'all_offers' => $allOffersForChannel,
];
})->toArray();
}
}

View File

@@ -46,12 +46,34 @@ class PriceListValues
{
foreach ($values as $value) {
$value['price_list_id'] = $price_list_id;
if ($value['price']) {
if (self::hasPrice($value)) {
self::store($value);
}
}
}
public static function purgeRemovedValues($price_list_id, array $ids)
{
if (! count($ids)) {
return;
}
PriceListValue::byPriceList($price_list_id)
->whereIn('id', $ids)
->delete();
}
protected static function hasPrice($value): bool
{
if (! array_key_exists('price', $value)) {
return false;
}
$price = $value['price'];
return $price !== null && $price !== '';
}
public static function getModel()
{
return PriceListValue::query();

View File

@@ -17,7 +17,7 @@ class PriceLists
'taxes' => Taxes::getOptions(),
'price_list' => [
'tariff_id' => $tariffId,
'price_list_values' => array_fill(0, 3, ''),
'price_list_values' => [],
],
];
}
@@ -50,9 +50,8 @@ class PriceLists
public static function edit($id)
{
$price_list = self::getFull($id)->toArray();
$n = count($price_list['price_list_values']);
if ($n <= 3) {
$price_list['price_list_values'] += array_fill($n, 3 - $n, '');
if (count($price_list['price_list_values']) === 0) {
$price_list['price_list_values'][] = [];
}
return $price_list;
@@ -71,9 +70,14 @@ class PriceLists
public static function store($data)
{
$id = $data['id'] ?? false;
$price_list_values = $data['price_list_values'] ?? false;
$price_list_values = $data['price_list_values'] ?? [];
$deleted_values = array_map('intval', array_filter($data['deleted_price_list_value_ids'] ?? [], function ($value) {
return $value !== null && $value !== '';
}));
unset($data['price_list_values']);
unset($data['deleted_price_list_value_ids']);
$price_list = $id ? self::update($data) : self::create($data);
PriceListValues::purgeRemovedValues($price_list->id, $deleted_values);
PriceListValues::storePrices($price_list->id, $price_list_values);
return $price_list;

View File

@@ -3,22 +3,53 @@
namespace App\Repositories\Shop;
use App\Models\Shop\SaleChannel;
use App\Repositories\Shop\Customers;
use App\Traits\Model\Basic;
class SaleChannels
{
use Basic;
public static function getDefaultID()
public static function getDefaultID($customerId = false)
{
$default = self::getDefault();
$default = self::getDefault($customerId);
return $default ? self::getDefault()->id : false;
return $default ? $default->id : false;
}
public static function getDefault()
public static function getDefault($customerId = false)
{
return self::getByCode('EXP');
$sessionChannelId = session('shop.default_sale_channel_id');
if ($sessionChannelId) {
$sessionChannel = SaleChannel::find($sessionChannelId);
if ($sessionChannel) {
return $sessionChannel;
}
}
$customer = $customerId ? Customers::get($customerId) : Customers::getAuth();
if ($customer) {
$customer->loadMissing('sale_channels');
if ($customer->default_sale_channel_id) {
$preferred = $customer->sale_channels->firstWhere('id', $customer->default_sale_channel_id);
if (! $preferred) {
$preferred = SaleChannel::find($customer->default_sale_channel_id);
}
if ($preferred) {
session(['shop.default_sale_channel_id' => $preferred->id]);
return $preferred;
}
}
if ($customer->sale_channels->isNotEmpty()) {
session(['shop.default_sale_channel_id' => $customer->sale_channels->first()->id]);
return $customer->sale_channels->first();
}
}
return self::getByCode('POSTE');
}
public static function getByCode($code)

View File

@@ -8,8 +8,14 @@ class Searches
{
public static function search($options)
{
// Get article IDs from Scout search
$searchResults = Article::search($options['search_name'])->get()->pluck('id');
// Filter to only include visible articles
$visibleArticleIds = Article::whereIn('id', $searchResults)->visible()->pluck('id');
return collect(Articles::getArticlesToSell([
'ids' => Article::search($options['search_name'])->get()->pluck('id'),
'ids' => $visibleArticleIds,
]))->sortBy('searchOrder')->toArray();
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Repositories\Shop;
use App\Repositories\Core\User\ShopCart;
class Shop
{
public static function getSaleChannelAvailabilitySummary(array $saleChannelIds): array
{
if (empty($saleChannelIds) || ShopCart::count() === 0) {
return [];
}
$cartItems = ShopCart::getContent();
$summary = [];
foreach ($saleChannelIds as $saleChannelId) {
$saleChannelId = (int) $saleChannelId;
$issues = [];
$issueCount = 0;
foreach ($cartItems as $item) {
$offerId = (int) $item->id;
if (! Offers::getPrice($offerId, 1, $saleChannelId)) {
$offer = Offers::get($offerId, ['article']);
$issues[] = $offer->article->name ?? $item->name;
$issueCount++;
if (count($issues) >= 3) {
continue;
}
}
}
if (! empty($issues)) {
$summary[$saleChannelId] = [
'full_count' => $issueCount,
'names' => array_slice($issues, 0, 3),
];
}
}
return $summary;
}
}

View File

@@ -1,23 +1,35 @@
<?php
return [
// --- The default avatar size
'size' => 80,
// Default configuration group for Gravatar
'default' => [
// --- The default avatar size
'size' => 80,
// --- The default avatar to display if we have no results
// (bool) false
// (string) 404
// (string) mm: (mystery-man) a simple, cartoon-style silhouetted outline of a person (does not vary by email hash).
// (string) identicon: a geometric pattern based on an email hash.
// (string) monsterid: a generated 'monster' with different colors, faces, etc.
// (string) wavatar: generated faces with differing features and backgrounds.
// (string) retro: awesome generated, 8-bit arcade-style pixelated faces.
'default' => 'identicon',
// --- The default avatar to display if we have no results
// (bool) false
// (string) 404
// (string) mm: (mystery-man) a simple, cartoon-style silhouetted outline of a person (does not vary by email hash).
// (string) identicon: a geometric pattern based on an email hash.
// (string) monsterid: a generated 'monster' with different colors, faces, etc.
// (string) wavatar: generated faces with differing features and backgrounds.
// (string) retro: awesome generated, 8-bit arcade-style pixelated faces.
'fallback' => 'identicon',
// --- Set the type of avatars we allow to show
// - g: suitable for display on all websites with any audience type.
// - pg: may contain rude gestures, provocatively dressed individuals, the lesser swear words, or mild violence.
// - r: may contain such things as harsh profanity, intense violence, nudity, or hard drug use.
// - x: may contain hardcore sexual imagery or extremely disturbing violence.
'maxRating' => 'g',
// --- Whether to use HTTPS protocol
'secure' => false,
// --- Set the type of avatars we allow to show
// - g: suitable for display on all websites with any audience type.
// - pg: may contain rude gestures, provocatively dressed individuals, the lesser swear words, or mild violence.
// - r: may contain such things as harsh profanity, intense violence, nudity, or hard drug use.
// - x: may contain hardcore sexual imagery or extremely disturbing violence.
'maximumRating' => 'g',
// --- Force default image to always display
'forceDefault' => false,
// --- Optional file extension appended to URL
'forceExtension' => 'jpg',
]
];

View File

@@ -126,7 +126,7 @@ return [
* The path where to store temporary files while performing image conversions.
* If set to null, storage_path('media-library/temp') will be used.
*/
'temporary_directory_path' => null,
'temporary_directory_path' => env('MEDIA_LIBRARY_TEMP_PATH', storage_path('media-library/temp')),
/*
* The engine that should perform the image conversions.

View File

@@ -0,0 +1,47 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (! Schema::hasTable('shop_deliveries')) {
return;
}
$columns = ['created_by', 'updated_by', 'deleted_by'];
$columnsToDrop = [];
foreach ($columns as $column) {
if (Schema::hasColumn('shop_deliveries', $column)) {
$columnsToDrop[] = $column;
}
}
if ($columnsToDrop) {
Schema::table('shop_deliveries', function (Blueprint $table) use ($columnsToDrop) {
$table->dropColumn($columnsToDrop);
});
}
Schema::table('shop_deliveries', function (Blueprint $table) {
$table->unsignedBigInteger('created_by')->nullable()->after('event_date_end');
$table->unsignedBigInteger('updated_by')->nullable()->after('created_by');
$table->unsignedBigInteger('deleted_by')->nullable()->after('updated_by');
});
}
public function down(): void
{
if (! Schema::hasTable('shop_deliveries')) {
return;
}
Schema::table('shop_deliveries', function (Blueprint $table) {
$table->dropColumn(['created_by', 'updated_by', 'deleted_by']);
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table('shop_customers', function (Blueprint $table) {
if (! Schema::hasColumn('shop_customers', 'default_sale_channel_id')) {
$table->unsignedInteger('default_sale_channel_id')->nullable()->after('settings');
$table->index('default_sale_channel_id', 'shop_customers_default_sale_channel_id_index');
}
});
}
public function down()
{
Schema::table('shop_customers', function (Blueprint $table) {
if (Schema::hasColumn('shop_customers', 'default_sale_channel_id')) {
$table->dropIndex('shop_customers_default_sale_channel_id_index');
$table->dropColumn('default_sale_channel_id');
}
});
}
};

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('shop_articles', function (Blueprint $table) {
$table->dropUnique('ref');
$table->unique(['ref', 'deleted_at'], 'shop_articles_ref_deleted_at_unique');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('shop_articles', function (Blueprint $table) {
$table->dropUnique('shop_articles_ref_deleted_at_unique');
$table->unique('ref');
});
}
};

2175
dump.sql

File diff suppressed because one or more lines are too long

View File

@@ -223,6 +223,9 @@ return [
'successmod' => 'Le canal de vente a été correctement modifié',
'successdel' => 'Le canal de vente a été correctement effacé',
'confirmdelete' => 'Confirmez-vous la suppression du canal de vente ?',
'missing_offers' => '{1} Ce canal de vente n\'a pas d\'offre pour :count produit.|[2,*] Ce canal de vente n\'a pas d\'offre pour :count produits.',
'missing_offers_all' => 'Ce canal de vente n\'a aucune offre pour tous les produits de votre panier.',
'cannot_select_with_cart' => 'Vous ne pouvez pas sélectionner ce mode d\'achat tant que votre panier contient des produits non disponibles dans ce mode.',
],
'shelves' => [
'title' => 'Rayons',

View File

@@ -4,8 +4,28 @@
'model' => 'customer_invoices',
'with_print' => false,
'with_filters' => false,
'show_callback' => 'AdminCustomerInvoiceView(id);',
])
<x-layout.modal title="Filtres" id="modal-customer_invoices-filters">
@include('Admin.Shop.CustomerInvoices.partials.filters', ['model' => 'customer_invoices'])
</x-layout.modal>
</x-card>
@include('load.layout.modal')
@push('js')
<script>
(function() {
const customerInvoiceShowTemplate = "{{ route('Shop.Invoices.view', ['uuid' => '__UUID__']) }}";
window.AdminCustomerInvoiceView = function(id) {
if (!id) {
return;
}
const url = customerInvoiceShowTemplate.replace('__UUID__', id);
openModal('Voir une facture', '#invoice-form', url, false, false, 'xl', true);
};
})();
</script>
@endpush

View File

@@ -15,6 +15,7 @@
<div class="row">
<div class="col-5">
{{ Form::label('active', __('Actif')) }}<br/>
<input type="hidden" name="active" value="0">
@include("components.form.toggle", [
'name' => 'active',
'value' => $delivery['active'] ?? false,
@@ -24,6 +25,7 @@
</div>
<div class="col-3">
{{ Form::label('is_public', __('Type')) }}
<input type="hidden" name="is_public" value="0">
@include('components.form.toggle', [
'name' => 'is_public',
'value' => $delivery['is_public'] ?? false,

View File

@@ -15,4 +15,9 @@
<td>
@include('components.form.inputs.money', ['name' => 'price_list_values[' . $index . '][price_taxed]', 'value' => $price_list_value['price_taxed'] ?? null, 'required' => true, 'class' => 'price_taxed'])
</td>
<td class="text-center align-middle">
<button type="button" class="btn btn-outline-danger btn-xs remove-price" title="{{ __('Supprimer') }}">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>

View File

@@ -30,12 +30,18 @@
<th>Unit. HT</th>
<th>TVA</th>
<th>Unit. TTC</th>
<th class="text-center" style="width: 60px;">Actions</th>
</tr>
</thead>
<tbody>
@foreach ($price_list['price_list_values'] as $price_list_value)
@include('Admin.Shop.PriceListValues.partials.row_price', ['index' => $loop->index])
@php($priceListValues = $price_list['price_list_values'] ?? [])
@php($nextIndex = count($priceListValues))
@foreach ($priceListValues as $index => $price_list_value)
@include('Admin.Shop.PriceListValues.partials.row_price', ['index' => $index, 'price_list_value' => $price_list_value])
@endforeach
@include('Admin.Shop.PriceListValues.partials.row_price', ['index' => $nextIndex, 'price_list_value' => []])
</tbody>
<tfoot>
<tr>
@@ -49,21 +55,30 @@
</tfoot>
</table>
@endcomponent
<div id="deleted-price-list-values"></div>
</form>
<script>
var priceRowIndex = 0;
var lastRemovedIndex = null;
function handleAddPrice() {
$('#add_price').click( function () {
var index = $('#prices-table tbody tr').length;
$.get("{{ route('Admin.Shop.PriceListValues.addPrice') }}/" + index, function(data) {
$("#prices-table").append(data);
})
handlePrices();
addEmptyPriceRow();
})
}
function addEmptyPriceRow() {
var index = nextPriceRowIndex();
$.get("{{ route('Admin.Shop.PriceListValues.addPrice') }}/" + index, function(data) {
$("#prices-table tbody").append(data);
handlePrices();
handleRemovePrice();
lastRemovedIndex = null;
});
}
function handle_prices() {
$('.price').change(function() {
$col_tax = $(this).parent().parent().find('.tax');
@@ -101,9 +116,82 @@
handle_prices_taxed();
}
function handleRemovePrice() {
$('#prices-table').off('click', '.remove-price').on('click', '.remove-price', function() {
var $row = $(this).closest('tr');
var idx = extractRowIndex($row);
var id = $row.find('input[name$="[id]"]').val();
if (id) {
registerDeletedPrice(id);
}
$row.remove();
lastRemovedIndex = idx;
ensureAtLeastOneRow();
});
}
function registerDeletedPrice(id) {
var $container = $('#deleted-price-list-values');
if ($container.find('input[value="' + id + '"]').length === 0) {
$container.append('<input type="hidden" name="deleted_price_list_value_ids[]" value="' + id + '">');
}
}
function ensureAtLeastOneRow() {
if ($('#prices-table tbody tr').length === 0) {
if (lastRemovedIndex !== null) {
$.get("{{ route('Admin.Shop.PriceListValues.addPrice') }}/" + lastRemovedIndex, function(data) {
$("#prices-table tbody").append(data);
handlePrices();
handleRemovePrice();
});
} else {
addEmptyPriceRow();
}
}
}
function computeStartingIndex() {
var maxIndex = -1;
$('#prices-table tbody tr').each(function() {
var $idInput = $(this).find('input[name$="[id]"]');
var name = $idInput.attr('name');
if (name) {
var matches = name.match(/price_list_values\[(\d+)\]\[id\]/);
if (matches && matches[1]) {
var idx = parseInt(matches[1], 10);
if (!isNaN(idx) && idx > maxIndex) {
maxIndex = idx;
}
}
}
});
return maxIndex + 1;
}
function nextPriceRowIndex() {
return priceRowIndex++;
}
function extractRowIndex($row) {
var $idInput = $row.find('input[name$="[id]"]');
var name = $idInput.attr('name');
if (!name) {
return priceRowIndex;
}
var matches = name.match(/price_list_values\[(\d+)\]\[id\]/);
return matches && matches[1] ? parseInt(matches[1], 10) : priceRowIndex;
}
$(function() {
priceRowIndex = computeStartingIndex();
handleAddPrice();
handlePrices();
handleRemovePrice();
ensureAtLeastOneRow();
});
</script>

View File

@@ -1,37 +1,59 @@
@if ($article['offers']['semences'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['semences'],
'title' => 'Semences',
'model' => 'semences',
'bgClass' => 'bg-green-light',
])
@endif
@php
// Check if article is not visible OR has no offers at all
$hasNoOffers = empty($article['offers']['semences'] ?? false)
&& empty($article['offers']['plants'] ?? false)
&& empty($article['offers']['legumes'] ?? false)
&& empty($article['offers']['marchandise'] ?? false);
$shouldShowComingSoon = !($article['visible'] ?? true) || $hasNoOffers;
@endphp
@if ($article['offers']['plants'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['plants'],
'title' => 'Plants',
'model' => 'plants',
'bgClass' => 'bg-green-light',
])
@endif
@if ($shouldShowComingSoon)
{{-- Display "Coming Soon" box when article is not visible or has no offers --}}
<div class="card border-info">
<div class="card-body text-center p-4">
<h4 class="text-info mb-3">
<i class="fas fa-clock"></i>
</h4>
<h5 class="card-title mb-0">Bientôt disponible</h5>
</div>
</div>
@else
{{-- Display normal offers for visible articles with available offers --}}
@if ($article['offers']['semences'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['semences'],
'title' => 'Semences',
'model' => 'semences',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['legumes'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['legumes'],
'title' => 'Légumes',
'model' => 'legumes',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['plants'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['plants'],
'title' => 'Plants',
'model' => 'plants',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['marchandise'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['marchandise'],
'title' => 'Marchandises',
'model' => 'marchandise',
'bgClass' => 'bg-green-light',
])
@if ($article['offers']['legumes'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['legumes'],
'title' => 'Légumes',
'model' => 'legumes',
'bgClass' => 'bg-green-light',
])
@endif
@if ($article['offers']['marchandise'] ?? false)
@include('Shop.Articles.partials.addBasket', [
'data' => $article['offers']['marchandise'],
'title' => 'Marchandises',
'model' => 'marchandise',
'bgClass' => 'bg-green-light',
])
@endif
@endif
@include('load.basket')

View File

@@ -48,9 +48,167 @@
</div>
<div class="col-lg-3 col-xs-12">
@if (auth('web')->check() && !empty($article['available_sale_channels']))
<div id="article-admin-offers" class="alert alert-info p-2 mb-3">
<strong class="d-block">Offres :</strong>
<ul class="list-unstyled mb-0 small">
@php
$currentSaleChannelId = $article['current_sale_channel']['id'] ?? null;
@endphp
@foreach ($article['available_sale_channels'] as $channel)
@php
$isCurrentChannel = $currentSaleChannelId === $channel['id'];
$priceTaxed = $channel['price_taxed'] ?? null;
$quantity = $channel['quantity'] ?? null;
$offerStock = $channel['offer_stock_current'] ?? null;
$offerIsActive = $channel['offer_is_active'] ?? false;
$offerHasStock = $channel['offer_has_stock'] ?? null;
$highlightStyle = $isCurrentChannel ? 'background-color: rgba(0, 0, 0, 0.06);' : '';
$nameClass = ($offerIsActive && $offerHasStock !== false) ? '' : 'text-muted';
$flags = [];
if (! $offerIsActive) {
$flags[] = 'inactive';
}
if ($offerHasStock === false) {
$flags[] = 'no-stock';
}
@endphp
<li style="{{ $highlightStyle }}">
<div class="d-flex justify-content-between align-items-start">
<span class="{{ $nameClass }}">
{{ $channel['name'] }}
<span class="d-block text-muted" style="font-size: 0.85em; padding-left: 0.9em;">
Code {{ $channel['code'] }}{!! $flags ? ' · <strong class="text-dark">'.implode('</strong> · <strong class="text-dark">', $flags).'</strong>' : '' !!}
</span>
</span>
@if ($priceTaxed !== null)
@php
$tariffId = $channel['tariff_id'] ?? null;
@endphp
@if ($tariffId)
<a href="{{ route('Admin.Shop.Tariffs.edit', $tariffId) }}" target="_blank" rel="noopener" class="ml-2 text-nowrap text-right {{ $nameClass }} text-decoration-none text-reset d-inline-block admin-link-group admin-price-link">
{{ number_format($priceTaxed, 2, ',', ' ') }} TTC
@if (! empty($quantity))
<span class="d-block text-muted" style="font-size: 0.85em;">Qté min. {{ $quantity }}</span>
@endif
</a>
@else
<span class="ml-2 text-nowrap text-right {{ $nameClass }}">
{{ number_format($priceTaxed, 2, ',', ' ') }} TTC
@if (! empty($quantity))
<span class="d-block text-muted" style="font-size: 0.85em;">Qté min. {{ $quantity }}</span>
@endif
</span>
@endif
@else
<span class="ml-2 text-muted"></span>
@endif
</div>
@if (!empty($channel['all_offers']))
<ul class="list-unstyled mb-0 mt-1" style="padding-left: 0.75em;">
@foreach ($channel['all_offers'] as $offer)
@php
$isSelectedOffer = $offer['id'] === $channel['offer_id'];
$offerClass = $offer['is_active'] ? 'text-dark' : 'text-muted';
$stockClass = $offer['stock_current'] > 0 ? 'text-success' : 'text-danger';
@endphp
<li class="small {{ $offerClass }}" style="font-size: 0.85em;">
<a href="{{ route('Admin.Shop.Offers.edit', $offer['id']) }}" target="_blank" rel="noopener" class="text-decoration-none {{ $offerClass }} admin-link-group admin-offer-link">
<div class="d-flex justify-content-between align-items-start">
<div>
<span style="opacity: 0.5;">{{ $isSelectedOffer ? '▸' : '○' }}</span>
@if ($offer['variation_name'])
{{ $offer['variation_name'] }}
@endif
- Stock: <strong class="{{ $stockClass }}">{{ $offer['stock_current'] }}</strong>
@if (!$offer['is_active'])
<span class="text-muted">(inactive)</span>
@endif
</div>
<div class="text-right text-nowrap ml-2">
{{ number_format($offer['price_taxed'], 2, ',', ' ') }}
@if ($offer['quantity'] > 1)
<span class="text-muted">(min {{ $offer['quantity'] }})</span>
@endif
</div>
</div>
</a>
</li>
@endforeach
</ul>
@endif
</li>
@endforeach
</ul>
</div>
@endif
@include('Shop.Articles.partials.ArticleAddBasket')
</div>
</div>
@endsection
@include('load.layout.modal')
@if (auth('web')->check() && !empty($article['available_sale_channels']))
@push('styles')
<style>
#article-admin-offers .admin-link-group {
transition: background-color 0.15s ease;
border-radius: 3px;
}
#article-admin-offers .admin-price-link {
display: inline-block;
padding: 2px 4px;
margin: -2px -4px;
}
#article-admin-offers .admin-offer-link {
display: block;
padding: 2px 4px;
margin: -2px -4px;
}
#article-admin-offers .admin-link-group:hover,
#article-admin-offers .admin-link-group:focus,
#article-admin-offers .admin-link-group.linked-hover {
background-color: rgba(0, 123, 255, 0.1);
text-decoration: none;
}
</style>
@endpush
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const container = document.getElementById('article-admin-offers');
if (!container) {
return;
}
const links = Array.from(container.querySelectorAll('a.admin-link-group[href]'));
const grouped = new Map();
links.forEach((link) => {
const href = link.getAttribute('href');
if (!grouped.has(href)) {
grouped.set(href, []);
}
grouped.get(href).push(link);
});
grouped.forEach((group) => {
group.forEach((link) => {
const addHighlight = () => group.forEach((item) => item.classList.add('linked-hover'));
const removeHighlight = () => group.forEach((item) => item.classList.remove('linked-hover'));
link.addEventListener('mouseenter', addHighlight);
link.addEventListener('mouseleave', removeHighlight);
link.addEventListener('focus', addHighlight);
link.addEventListener('blur', removeHighlight);
});
});
});
</script>
@endpush
@endif

View File

@@ -58,7 +58,7 @@
updateBasket(offer_id, quantity, function() {
calculatePrice($row);
calculateTotal();
});
}, $row);
});
$('.basket-delete').click(function() {
@@ -70,13 +70,20 @@
});
}
function updateBasket(offer_id, quantity, callback) {
function updateBasket(offer_id, quantity, callback, $row) {
var data = {
offer_id: offer_id,
quantity: quantity,
update: true
};
$.post("{{ route('Shop.Basket.addBasket') }}", data, callback);
$.post("{{ route('Shop.Basket.addBasket') }}", data, function(response) {
if ($row && response && response.added && typeof response.added.price !== 'undefined') {
$row.find('.basket-price').text(fixNumber(response.added.price));
$row.find('.basket-total-row').text(fixNumber(response.added.price * $row.find('.basket-quantity').val()));
}
callback(response);
refreshBasketTop();
});
}
function calculatePrice($that) {

View File

@@ -1,25 +1,174 @@
@foreach ($deliveries as $delivery)
<div class="row">
<div class="col-1 text-right pt-1">
@push('styles')
<style>
.sale-channel-wrapper {
border: none;
background-color: transparent;
}
.sale-channel-wrapper:not(.blocked) .card-body {
border: 1px solid #e5e7eb;
border-radius: .75rem;
background-color: #ffffff;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.sale-channel-wrapper:not(.blocked) .card-body:hover {
border-color: #3b82f6;
box-shadow: 0 0.35rem 0.8rem rgba(37, 99, 235, 0.12);
}
.sale-channel-wrapper.blocked .card-body {
border: 1px solid #d1d5db;
border-radius: .75rem;
background-color: #f3f4f6;
}
.sale-channel-wrapper .card-body {
padding: 1.25rem;
}
.sale-channel-toggle {
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 0.25rem;
}
.sale-channel-content strong {
font-size: 1.05rem;
}
.sale-channel-warning {
font-size: 0.85rem;
}
.sale-channel-wrapper .icheck-success > input:first-child + label::before,
.sale-channel-wrapper .icheck-primary > input:first-child + label::before,
.sale-channel-wrapper .icheck-danger > input:first-child + label::before {
opacity: 1;
border-width: 2px;
border-color: #9ca3af;
}
.sale-channel-wrapper.blocked .icheck-success > input:first-child + label::before,
.sale-channel-wrapper.blocked .icheck-primary > input:first-child + label::before,
.sale-channel-wrapper.blocked .icheck-danger > input:first-child + label::before {
border-color: #cbd5f5;
background-color: #f8fafc;
}
.sale-channel-wrapper .icheck-success > input:first-child + label,
.sale-channel-wrapper .icheck-primary > input:first-child + label,
.sale-channel-wrapper .icheck-danger > input:first-child + label {
opacity: 1;
}
.sale-channel-wrapper [class*="icheck-"] > input:first-child:disabled + label {
opacity: 1;
}
</style>
@endpush
@php
$saleChannelsCollection = collect($sale_channels);
$firstSaleChannel = $saleChannelsCollection->first();
$selectedSaleChannelId = $customer['default_sale_channel_id'] ?? ($firstSaleChannel['id'] ?? null);
$cartCount = app('App\\Repositories\\Core\\User\\ShopCart')::count();
@endphp
@if ($cartCount > 0)
<div class="alert alert-warning">
<strong>Note :</strong> en changeant votre mode d'achat, les articles de votre panier seront transférés sur la liste de prix correspondant au nouveau canal de vente sélectionné.
</div>
@endif
@foreach ($saleChannelsCollection as $saleChannel)
@php
$check = $sale_channel_checks[$saleChannel['id']] ?? null;
$isBlocked = $check && $saleChannel['id'] !== $selectedSaleChannelId;
@endphp
<div class="card sale-channel-wrapper mb-3 @if($isBlocked) blocked @endif">
<div class="card-body py-3">
<div class="row align-items-start">
<div class="col-1 sale-channel-toggle">
@include('components.form.radios.icheck', [
'name' => 'delivery_id',
'id_name' => 'delivery_id_' . $delivery['id'],
'value' => $delivery['id'],
'checked' => $customer['sale_delivery_id'] ?? false,
'class' => 'delivery',
'name' => 'sale_channel_id',
'id_name' => 'sale_channel_id_' . $saleChannel['id'],
'val' => $saleChannel['id'],
'value' => $selectedSaleChannelId,
'class' => 'sale-channel',
'disabled' => $isBlocked,
])
</div>
<div class="col-11 pt-3">
<strong>{{ $delivery['name'] }} - {{ $delivery['sale_channel']['name'] }}</strong><br />
<p>{{ $delivery['description'] }}</p>
</div>
<div class="col-11 sale-channel-content @if($isBlocked) text-muted @endif">
<strong>{{ $saleChannel['name'] }}</strong><br />
<p class="mb-2">{!! $saleChannel['description'] ?? '' !!}</p>
@if ($check)
<div class="text-danger small mb-0 sale-channel-warning">
@php $missingCount = $check['full_count'] ?? count($check['names']); @endphp
@if ($cartCount > 0 && $missingCount >= $cartCount)
{{ __('shop.sale_channels.missing_offers_all') }}
@else
{{ trans_choice('shop.sale_channels.missing_offers', $missingCount, ['count' => $missingCount]) }}
<br>
@if ($missingCount > 3)
<span class="d-block">{{ implode(', ', array_slice($check['names'], 0, 3)) }}, …</span>
@else
<span class="d-block">{{ implode(', ', $check['names']) }}</span>
@endif
@endif
<div class="d-flex align-items-start mt-1">
<span class="mr-1">⚠️</span>
<span>{{ __('shop.sale_channels.cannot_select_with_cart') }}</span>
</div>
</div>
@endif
</div>
</div>
</div>
</div>
@endforeach
@push('js')
<script>
$('.delivery').off().change(function() {
console.log($(this).val());
const $saleChannels = $('.sale-channel');
const updateUrl = '{{ route('Shop.Customers.storeProfileAjax') }}';
const token = '{{ csrf_token() }}';
const customerId = {{ $customer['id'] ?? 'null' }};
let currentSaleChannelId = '{{ $selectedSaleChannelId }}';
$saleChannels.off().change(function() {
if (!customerId) {
return;
}
const selectedSaleChannel = $(this).val();
$.post(updateUrl, {
_token: token,
id: customerId,
default_sale_channel_id: selectedSaleChannel,
}).done(function() {
currentSaleChannelId = selectedSaleChannel;
window.location.reload();
}).fail(function(xhr) {
const message = xhr.responseJSON && xhr.responseJSON.message
? xhr.responseJSON.message
: "{{ __('Une erreur est survenue lors de l\'enregistrement du canal de vente préféré.') }}";
console.error('Sale channel update failed', {
status: xhr.status,
response: xhr.responseJSON || xhr.responseText,
selectedSaleChannel
});
alert(message);
if (currentSaleChannelId) {
$saleChannels.filter('[value="' + currentSaleChannelId + '"]').prop('checked', true);
}
});
});
</script>
@endpush

View File

@@ -1,35 +1,69 @@
<nav>
<div class="nav nav-tabs pl-2">
<a href="#deliveriesTab" data-toggle="tab" class="nav-item nav-link active" role="tab" aria-selected="true">
MON MODE D'ACHAT
</a>
<a href="#ordersTab" data-toggle="tab" class="nav-item nav-link" role="tab" aria-selected="false">
SUIVI DE COMMANDES
</a>
<a href="#invoicesTab" data-toggle="tab" class="nav-item nav-link" role="tab" aria-selected="false">
FACTURES
</a>
</div>
</nav>
@php
$saleChannels = $sale_channels ?? [];
@endphp
<div class="tab-content">
<div class="tab-pane fade show active pt-0 pb-0" id="deliveriesTab">
<x-card classBody="bg-light">
@include('Shop.Customers.partials.deliveries')
</x-card>
@if (count($saleChannels) > 1)
<nav>
<div class="nav nav-tabs pl-2">
<a href="#deliveriesTab" data-toggle="tab" class="nav-item nav-link active" role="tab" aria-selected="true">
MON MODE D'ACHAT
</a>
<a href="#ordersTab" data-toggle="tab" class="nav-item nav-link" role="tab" aria-selected="false">
SUIVI DE COMMANDES
</a>
<a href="#invoicesTab" data-toggle="tab" class="nav-item nav-link" role="tab" aria-selected="false">
FACTURES
</a>
</div>
</nav>
<div class="tab-content">
<div class="tab-pane fade show active pt-0 pb-0" id="deliveriesTab">
<x-card classBody="bg-light">
@include('Shop.Customers.partials.deliveries')
</x-card>
</div>
<div class="tab-pane fade show pt-0 pb-0" id="ordersTab">
<x-card classBody="bg-light">
@include('Shop.Orders.partials.list', [
'dataTable' => $orders,
])
</x-card>
</div>
<div class="tab-pane fade show pt-0 pb-0" id="invoicesTab">
<x-card classBody="bg-light">
@include('Shop.Invoices.partials.list', [
'dataTable' => $invoices,
])
</x-card>
</div>
</div>
<div class="tab-pane fade show pt-0 pb-0" id="ordersTab">
<x-card classBody="bg-light">
@include('Shop.Orders.partials.list', [
'dataTable' => $orders,
])
</x-card>
@else
<nav>
<div class="nav nav-tabs pl-2">
<a href="#ordersTab" data-toggle="tab" class="nav-item nav-link active" role="tab" aria-selected="true">
SUIVI DE COMMANDES
</a>
<a href="#invoicesTab" data-toggle="tab" class="nav-item nav-link" role="tab" aria-selected="false">
FACTURES
</a>
</div>
</nav>
<div class="tab-content">
<div class="tab-pane fade show active pt-0 pb-0" id="ordersTab">
<x-card classBody="bg-light">
@include('Shop.Orders.partials.list', [
'dataTable' => $orders,
])
</x-card>
</div>
<div class="tab-pane fade show pt-0 pb-0" id="invoicesTab">
<x-card classBody="bg-light">
@include('Shop.Invoices.partials.list', [
'dataTable' => $invoices,
])
</x-card>
</div>
</div>
<div class="tab-pane fade show pt-0 pb-0" id="invoicesTab">
<x-card classBody="bg-light">
@include('Shop.Invoices.partials.list', [
'dataTable' => $invoices,
])
</x-card>
</div>
</div>
@endif

View File

@@ -50,7 +50,7 @@
@include('load.layout.chevron')
@push('js')
@prepend('js')
<script>
$('#customer').click(function() {
$(".personal_data").addClass('d-none');
@@ -65,7 +65,9 @@
});
function refreshBasketTotal(deliveryId, deliveryTypeId) {
options = deliveryId + '/' + deliveryTypeId;
var safeDeliveryId = deliveryId !== undefined && deliveryId !== null ? deliveryId : '';
var safeDeliveryTypeId = deliveryTypeId !== undefined && deliveryTypeId !== null ? deliveryTypeId : '';
var options = safeDeliveryId + '/' + safeDeliveryTypeId;
$.get("{{ Route('Shop.Basket.getBasketTotal') }}/" + options, function(data) {
$('#basketTotal').html(data);
});
@@ -73,4 +75,4 @@
initChevron();
</script>
@endpush
@endprepend

View File

@@ -1,13 +1,51 @@
@php
$addresses = collect($addresses ?? []);
$preselectedAddressId = old($name);
if ($preselectedAddressId === null && is_string($name) && str_contains($name, '[')) {
$dotName = preg_replace('/\[(.*?)\]/', '.$1', $name);
$dotName = trim($dotName, '.');
$preselectedAddressId = $dotName ? old($dotName) : null;
}
if (($preselectedAddressId === null || $preselectedAddressId === '') && $addresses->isNotEmpty()) {
$defaultAddress = $addresses->firstWhere('priority', 1);
if (! $defaultAddress) {
$defaultAddress = $addresses
->filter(function ($address) {
return (int) ($address['priority'] ?? 0) > 0;
})
->sortByDesc(function ($address) {
return (int) ($address['priority'] ?? 0);
})
->first();
}
if (! $defaultAddress) {
$defaultAddress = $addresses->firstWhere('is_default', true)
?? $addresses->firstWhere('default', true);
}
if (! $defaultAddress) {
$defaultAddress = $addresses->first();
}
$preselectedAddressId = $defaultAddress['id'] ?? null;
}
$addresses = $addresses->all();
@endphp
@if ($addresses)
@foreach ($addresses ?? [] as $address)
@foreach ($addresses as $address)
<div class="row mb-3">
<div class="col-1">
@include('components.form.radios.icheck', [
'name' => $name,
'val' => $address['id'],
'id' => $prefix . '_address_' . $address['id'],
'value' =>
old($name) ?? $address['priority'] || count($addresses) === 1 ? $address['id'] : false,
'value' => $preselectedAddressId,
])
</div>
<div class="col-11">

View File

@@ -1,10 +1,20 @@
@php
$defaultSaleChannelId = $customer['default_sale_channel_id'] ?? null;
$preselectedDeliveryId = old('delivery_id');
if (! $preselectedDeliveryId && $defaultSaleChannelId) {
$match = collect($deliveries)->firstWhere('sale_channel_id', $defaultSaleChannelId);
$preselectedDeliveryId = $match['id'] ?? null;
}
@endphp
@foreach ($deliveries as $delivery)
<div class="row">
<div class="col-1">
@include('components.form.radios.icheck', [
'name' => 'delivery_id',
'val' => $delivery['id'],
'value' => (int) old('delivery_id') === $delivery['id'] ? $delivery['id'] : null,
'value' => $preselectedDeliveryId,
'id' => 'delivery_' . $delivery['id'],
'class' => 'delivery_mode' . ($delivery['at_house'] ? ' at_house' : ''),
])
@@ -29,16 +39,29 @@ ci-contre
@push('js')
<script>
function handleDeliveries() {
$('#delivery_mode input.delivery_mode').change(function() {
if ($(this).hasClass('at_house')) {
var $deliveryInputs = $('#delivery_mode input.delivery_mode');
$deliveryInputs.change(function() {
var $currentDelivery = $(this);
var deliveryTypeId = $('input[name=delivery_type_id]:checked').val();
if ($currentDelivery.hasClass('at_house')) {
$('#delivery_addresses').closest('.card').removeClass('d-none');
var deliveryTypeId = $('input[name=delivery_type_id]:checked').val()
} else {
$('#delivery_addresses').closest('.card').addClass('d-none');
}
var deliveryId = $(this).val();
var deliveryId = $currentDelivery.val();
refreshBasketTotal(deliveryId, deliveryTypeId);
});
var $preselected = $deliveryInputs.filter(':checked').first();
if ($preselected.length) {
$preselected.trigger('change');
} else {
$('#delivery_addresses').closest('.card').addClass('d-none');
}
}
handleDeliveries();

View File

@@ -7,7 +7,18 @@
</th>
</tr>
</thead>
@foreach ($delivery_types as $delivery_type_id => $delivery_type)
@php
$deliveryTypes = collect($delivery_types);
$preselectedDeliveryTypeId = old('delivery_type_id');
if ($preselectedDeliveryTypeId === null || $preselectedDeliveryTypeId === '') {
$preselectedDeliveryTypeId = $deliveryTypes->keys()->first();
}
$deliveryTypes = $deliveryTypes->all();
@endphp
@foreach ($deliveryTypes as $delivery_type_id => $delivery_type)
<tr>
<td>
@include('components.form.radios.icheck', [
@@ -15,6 +26,7 @@
'val' => $delivery_type_id,
'id' => 'delivery_type_' . $delivery_type_id,
'class' => 'delivery_type',
'value' => $preselectedDeliveryTypeId,
])
</td>
<td>
@@ -31,11 +43,19 @@
@push('js')
<script>
function handleDeliveryTypes() {
$('input.delivery_type').change(function() {
var $deliveryTypeInputs = $('input.delivery_type');
$deliveryTypeInputs.change(function() {
var deliveryTypeId = $(this).val();
var deliveryId = $('input[name=delivery_id]:checked').val()
refreshBasketTotal(deliveryId, deliveryTypeId);
});
var $preselected = $deliveryTypeInputs.filter(':checked').first();
if ($preselected.length) {
$preselected.trigger('change');
}
}
handleDeliveryTypes();
</script>

View File

@@ -17,6 +17,7 @@
<link rel="shortcut icon" type="image/x-icon" href="{{ asset('img/favicon.ico') }}">
<link rel="stylesheet" href="/css/site.min.css?{{ date('Ymd') }}" type="text/css" media="all">
@stack('styles')
@stack('css')
</head>

View File

@@ -38,7 +38,8 @@
/<(p|a|div|span|strike|strong|i|u)[^>]*?>(\s|&nbsp;|<br\/>|\r|\n)*?<\/(p|a|div|span|strike|strong|i|u)>/gi,
''); // Empty tags
},
skin: "boilerplate",
skin: "oxide",
content_css: 'oxide',
language: '{{ App::getLocale() }}',
file_picker_callback: function(callback, value, meta) {
var x = window.innerWidth || document.documentElement.clientWidth || document.getElementsByTagName(

View File

@@ -9,14 +9,16 @@
var selector = (typeof(sel) == 'undefined') ? '.save' : sel;
$(selector).off().click(function(e) {
if (typeof initValidator === 'function') {
console.log('click');
e.preventDefault();
if ($(form).valid()) {
$(this).prop("disabled", true);
$(this).html($(this).data('loading-text'));
$(form).submit();
} else {
console.log('erreur');
const $firstInvalid = $(form).find(':invalid, .error').first();
if ($firstInvalid.length) {
$firstInvalid.focus();
}
}
} else {
$(form).submit();

View File

@@ -75,12 +75,12 @@
if (typeof(tinyMCE) != 'undefined') {
tinyMCE.triggerSave();
}
$('form ' + form_id).submit();
getModalForm(form_id).trigger('submit');
status = 1;
}
function handlePostModal(form_id, url_save, callback) {
$('form ' + form_id).submit(function(e) {
getModalForm(form_id).off('submit').on('submit', function(e) {
e.preventDefault();
var formData = new FormData(this);
$.ajax({
@@ -98,6 +98,15 @@
});
});
}
function getModalForm(form_id) {
var $form = $(form_id);
if (! $form.length) {
$form = $('form' + form_id);
}
return $form;
}
</script>
@endpush

View File

@@ -3,5 +3,4 @@
Route::prefix('CustomerInvoices')->name('CustomerInvoices.')->group(function () {
Route::get('', 'CustomerInvoiceController@index')->name('index');
Route::delete('destroy/{id?}', 'CustomerInvoiceController@destroy')->name('destroy');
Route::get('view/{id?}', 'CustomerInvoiceController@view')->name('view');
});