From f96154d5fe094995835e9adcb49398ba1eaa9e53 Mon Sep 17 00:00:00 2001 From: Donovan Lambert Date: Tue, 5 Jan 2021 11:50:17 +0100 Subject: [PATCH] OSS => Premium MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 2dc074ab2530db947b7035d1b3d4f3c5c8c2c541 Author: Paul Maillardet Date: Tue Jan 5 11:47:59 2021 +0100 Ajoute des onglets dans la vue du matériel en détail (#82) * Ajoute des onglets dans la vue du matériel en détail * Renomme le Dotfile de développement * Supprime l'affichage des unités dans la vue d'un matériel Co-authored-by: Donovan Lambert commit 27e467d25ce2e5c5e69b72b54f0eb4ada8a0ad13 Merge: d318c40 0b8c817 Author: Paul Maillardet Date: Mon Jan 4 21:37:03 2021 +0100 Merge pull request #80 from Robert-2/feature/material-view Ajoute la vue d'un matériel commit 0b8c817a13bac798d96b88d5e6323c685ff010b0 Author: Paul Maillardet Date: Sun Jan 3 22:27:42 2021 +0100 Corrige selon review commit 33cdf3d8cf9907154d5e25f646e75661d380ea2f Author: Paul Maillardet Date: Thu Dec 31 02:42:06 2020 +0100 Ajoute la vue d'un matériel commit d318c407f4266c7231d4fc66b129fb61c2f62a40 Merge: 985126a 3053393 Author: Paul Maillardet Date: Sat Jan 2 23:18:58 2021 +0100 Merge pull request #81 from Robert-2/feature/improves-validation-2 Améliore (encore) la validation commit 30533932ad9f82df849d1559e6d2f1e6c6efed21 Author: Donovan Lambert Date: Fri Jan 1 16:35:37 2021 +0100 Corrige les tests unitaires vu la nouvelle année (2021) commit 37149e4e202ca2adba2f4415c27d0d0f96f237b5 Author: Donovan Lambert Date: Fri Jan 1 16:14:05 2021 +0100 Améliore (encore) la validation commit 985126a64b611b91a1b4aae2b3b26ee2e2abca51 Merge: acbb5cf de0bcb8 Author: Paul Maillardet Date: Wed Dec 30 14:04:04 2020 +0100 Merge pull request #79 from Robert-2/feature/remove-partial-validation Améliore la validation des modèles commit de0bcb85b7d76a04ae5bbe48798e14cf9c86f5b4 Author: Donovan Lambert Date: Wed Dec 30 12:25:45 2020 +0100 Corrige la validation des modèles commit 57993a98b0e8fd17fdbfddd84995753eb53ae9c3 Author: Donovan Lambert Date: Tue Dec 29 18:56:47 2020 +0100 Amélioration des booléens dans les fixtures commit acbb5cf06131134a1852b6ed69987a3d3bcd95f1 Author: Donovan Date: Tue Dec 29 17:27:42 2020 +0100 Améliore la validation (+ Refactoring modèles) (#76) commit f0bbab2fe77b7b359a3eaef8aabb23823d45a7b5 Author: Donovan Date: Tue Dec 29 11:25:22 2020 +0100 Ajuste la contrainte des unités de matériel dans les événements (#77) (Sera rollback lorsque l'on prendra en charge les guards lors du switch-back Gestion unitaire => Gestion non unitaire) --- CHANGELOG.md | 2 + .../components/MaterialTags/MaterialTags.scss | 15 +++ .../components/MaterialTags/MaterialTags.vue | 26 ++++++ client/src/locale/en/common.js | 9 ++ client/src/locale/en/pages.js | 1 + client/src/locale/fr/common.js | 9 ++ client/src/locale/fr/pages.js | 1 + client/src/pages/Material/index.js | 2 +- .../Infos/Attributes/Attributes.scss | 11 +++ .../Infos/Attributes/Attributes.vue | 44 +++++++++ .../src/pages/MaterialView/Infos/Infos.scss | 35 +++++++ client/src/pages/MaterialView/Infos/Infos.vue | 93 +++++++++++++++++++ client/src/pages/MaterialView/Infos/index.js | 56 +++++++++++ .../src/pages/MaterialView/MaterialView.scss | 8 ++ .../src/pages/MaterialView/MaterialView.vue | 28 ++++++ client/src/pages/MaterialView/index.js | 66 +++++++++++++ client/src/pages/Materials/Materials.scss | 15 +-- client/src/pages/Materials/Materials.vue | 21 +++-- client/src/pages/Materials/index.js | 5 +- client/src/router.js | 12 +++ client/src/style/vendors/_vue-slim-tabs.scss | 2 +- server/src/App/Validation/Rules/Callback.php | 39 +++++++- server/tests/endpoints/BillsTest.php | 7 +- server/tests/models/BillTest.php | 6 +- 24 files changed, 479 insertions(+), 34 deletions(-) create mode 100644 client/src/components/MaterialTags/MaterialTags.scss create mode 100644 client/src/components/MaterialTags/MaterialTags.vue create mode 100644 client/src/pages/MaterialView/Infos/Attributes/Attributes.scss create mode 100644 client/src/pages/MaterialView/Infos/Attributes/Attributes.vue create mode 100644 client/src/pages/MaterialView/Infos/Infos.scss create mode 100644 client/src/pages/MaterialView/Infos/Infos.vue create mode 100644 client/src/pages/MaterialView/Infos/index.js create mode 100644 client/src/pages/MaterialView/MaterialView.scss create mode 100644 client/src/pages/MaterialView/MaterialView.vue create mode 100644 client/src/pages/MaterialView/index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0afa66662..0944833f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Ce projet adhère au principe du [Semantic Versioning](https://semver.org/spec/v (Un lien symbolique est utilisé côté serveur pour relier les deux côtés de l'application) - Corrige l'hôte de développement et permet sa customisation via une variable d'environnement. - Améliorations internes de la validation des données. +- Ajoute une page de vue du matériel en détail. +- Utilise des onglets dans la page de vue du matériel. ## 0.10.2 (2020-11-16) diff --git a/client/src/components/MaterialTags/MaterialTags.scss b/client/src/components/MaterialTags/MaterialTags.scss new file mode 100644 index 000000000..7686f164f --- /dev/null +++ b/client/src/components/MaterialTags/MaterialTags.scss @@ -0,0 +1,15 @@ +.MaterialTags { + display: flex; + flex-wrap: wrap; + align-items: center; + padding: 0; + margin: 0; + + &__item { + list-style: none; + padding: .3rem .5rem; + margin: 0 .35rem .35rem 0; + background: $bg-color-emphasis; + border-radius: 10px; + } +} diff --git a/client/src/components/MaterialTags/MaterialTags.vue b/client/src/components/MaterialTags/MaterialTags.vue new file mode 100644 index 000000000..daa55a04f --- /dev/null +++ b/client/src/components/MaterialTags/MaterialTags.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/client/src/locale/en/common.js b/client/src/locale/en/common.js index 78739ee0a..963d9428d 100644 --- a/client/src/locale/en/common.js +++ b/client/src/locale/en/common.js @@ -8,6 +8,7 @@ export default { 'action-add': "Add", 'action-edit': "Edit", + 'action-view': "Display details", 'action-trash': "Trash bin", 'action-restore': "Restore", 'action-delete': "Delete", @@ -78,21 +79,28 @@ export default { 'ref': "Ref.", 'reference': "Reference", 'park': "Park", + 'prices': "Prices", 'rental-price': "Rental price", 'replacement-price': "Replacement price", 'rent-price': "Rent. price", 'repl-price': "Repl. price", + 'value-per-day': '{value}\u00a0/\u00a0day', 'serial-number': "Serial n°", 'quantity': "Stock qty", + 'quantities': "Quantities", 'quantity-out-of-order': "Out of order qty", 'discountable': "Discountable?", + 'material-is-discountable': "The material is «\u0a00discountable\u00a0»: a discount amount can be applied to this material.", 'hidden-on-bill': "Hidden on bill?", + 'material-not-displayed-on-bill': "The material is not displayed on bills.", 'price-must-be-zero': "the rental price must be 0", 'all-parks': "All parks combined", 'all-categories': "All categories", 'all-sub-categories': "All sub-categories", 'open-trash-bin': "Display trash bin", 'display-not-deleted-items': "Display not deleted items", + 'created-at': "Created at:", + 'updated-at': "Updated at:", 'event-details': "Event's details", 'title': "Title", @@ -147,6 +155,7 @@ export default { "{count} items", ], 'stock-items-count': "{count} in stock", + 'out-of-order-items-count': "{count} out of order", 'sub-total': "Sub-total", 'total-amount': "Total amount", 'total-amount-with-discount': "Total with discount", diff --git a/client/src/locale/en/pages.js b/client/src/locale/en/pages.js index e05ad4106..33c9067e7 100644 --- a/client/src/locale/en/pages.js +++ b/client/src/locale/en/pages.js @@ -137,6 +137,7 @@ export default { 'add': "New material", 'edit': "Modify material «\u00a0{pageSubTitle}\u00a0»", 'help-edit': "Give a short name, and use the description field to detail the material if needed.", + 'view': "Details of material «\u00a0{pageSubTitle}\u00a0»", 'confirm-delete': "Move this material in trash bin?", 'confirm-permanently-delete': "Do you really want to permanently delete this material?", 'confirm-restore': "Do you really want to restore this material?", diff --git a/client/src/locale/fr/common.js b/client/src/locale/fr/common.js index 509ff0bdc..40e18c70d 100644 --- a/client/src/locale/fr/common.js +++ b/client/src/locale/fr/common.js @@ -8,6 +8,7 @@ export default { 'action-add': "Ajouter", 'action-edit': "Modifier", + 'action-view': "Afficher en détail", 'action-trash': "Corbeille", 'action-restore': "Restaurer", 'action-delete': "Supprimer", @@ -78,21 +79,28 @@ export default { 'ref': "Réf.", 'reference': "Référence", 'park': "Parc", + 'prices': "Tarifs", 'rental-price': "Tarif location", 'replacement-price': "Prix de remplacement", 'rent-price': "Tarif loc.", 'repl-price': "Val. rempl.", + 'value-per-day': '{value}\u00a0/\u00a0jour', 'serial-number': "N° de série", 'quantity': "Qté stock", + 'quantities': "Quantités", 'quantity-out-of-order': "Qté en panne", 'discountable': "Remisable\u00a0?", + 'material-is-discountable': "Le matériel est «\u00a0remisable\u00a0»\u00a0: une remise peut être appliquée sur ce matériel.", 'hidden-on-bill': "Caché sur la facture\u00a0?", + 'material-not-displayed-on-bill': "Le matériel n'est pas affiché sur les factures.", 'price-must-be-zero': "le tarif de location doit être égal à 0", 'all-parks': "Tous parcs confondus", 'all-categories': "Toutes catégories", 'all-sub-categories': "Toutes sous-catégories", 'open-trash-bin': "Afficher la corbeille", 'display-not-deleted-items': "Afficher les enregistrements non supprimés", + 'created-at': "Créé le\u00a0:", + 'updated-at': "Modifié le\u00a0:", 'event-details': "Détails de l'événement", 'title': "Titre", @@ -147,6 +155,7 @@ export default { "{count} articles", ], 'stock-items-count': "{count} en stock", + 'out-of-order-items-count': "{count} en panne", 'sub-total': "Sous-total", 'total-amount': "Montant total", 'total-amount-with-discount': "Total après remise", diff --git a/client/src/locale/fr/pages.js b/client/src/locale/fr/pages.js index 5de64119d..d240f025a 100644 --- a/client/src/locale/fr/pages.js +++ b/client/src/locale/fr/pages.js @@ -137,6 +137,7 @@ export default { 'add': "Nouveau matériel", 'edit': "Modifier le matériel «\u00a0{pageSubTitle}\u00a0»", 'help-edit': "Trouvez un nom assez court, et utilisez plutôt la description pour détailler le matériel si besoin.", + 'view': "Détails du matériel «\u00a0{pageSubTitle}\u00a0»", 'confirm-delete': "Mettre ce matériel à la corbeille\u00a0?", 'confirm-permanently-delete': "Voulez-vous vraiment supprimer définitivement ce matériel\u00a0?", 'confirm-restore': "Voulez-vous vraiment restaurer ce matériel\u00a0?", diff --git a/client/src/pages/Material/index.js b/client/src/pages/Material/index.js index 4a9866401..4be460277 100644 --- a/client/src/pages/Material/index.js +++ b/client/src/pages/Material/index.js @@ -149,7 +149,7 @@ export default { this.setMaterialData(data); setTimeout(() => { - this.$router.push('/materials'); + this.$router.push(`/materials/${data.id}/view`); }, 300); }) .catch(this.displayError); diff --git a/client/src/pages/MaterialView/Infos/Attributes/Attributes.scss b/client/src/pages/MaterialView/Infos/Attributes/Attributes.scss new file mode 100644 index 000000000..c1686ec51 --- /dev/null +++ b/client/src/pages/MaterialView/Infos/Attributes/Attributes.scss @@ -0,0 +1,11 @@ +.MaterialViewInfosAttributes { + &__list { + &__item { + margin-bottom: .3rem; + + &__value { + font-weight: 700; + } + } + } +} diff --git a/client/src/pages/MaterialView/Infos/Attributes/Attributes.vue b/client/src/pages/MaterialView/Infos/Attributes/Attributes.vue new file mode 100644 index 000000000..9014ec04b --- /dev/null +++ b/client/src/pages/MaterialView/Infos/Attributes/Attributes.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/client/src/pages/MaterialView/Infos/Infos.scss b/client/src/pages/MaterialView/Infos/Infos.scss new file mode 100644 index 000000000..9a04df2b4 --- /dev/null +++ b/client/src/pages/MaterialView/Infos/Infos.scss @@ -0,0 +1,35 @@ +.MaterialViewInfos { + display: flex; + flex-wrap: wrap; + + &__main { + flex: 2; + min-width: 250px; + margin-right: 3rem; + } + + &__extras { + flex: 1; + min-width: 250px; + padding: 1rem 0 0; + } + + &__rental-price, + &__stock-quantity { + font-weight: 700; + margin-bottom: .3rem; + } + + &__out-of-order { + color: $text-danger-color; + } + + &__categories { + margin: 1.5rem 0; + } + + &__dates { + color: $text-light-color; + margin-top: 1.5rem; + } +} diff --git a/client/src/pages/MaterialView/Infos/Infos.vue b/client/src/pages/MaterialView/Infos/Infos.vue new file mode 100644 index 000000000..f5d8b5661 --- /dev/null +++ b/client/src/pages/MaterialView/Infos/Infos.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/client/src/pages/MaterialView/Infos/index.js b/client/src/pages/MaterialView/Infos/index.js new file mode 100644 index 000000000..442e8cd60 --- /dev/null +++ b/client/src/pages/MaterialView/Infos/index.js @@ -0,0 +1,56 @@ +import moment from 'moment'; +import Config from '@/config/globalConfig'; +import formatAmount from '@/utils/formatAmount'; +import store from '@/store'; +import MaterialTags from '@/components/MaterialTags/MaterialTags.vue'; +import Attributes from './Attributes/Attributes.vue'; + +export default { + name: 'MaterialViewInfos', + components: { + Attributes, + MaterialTags, + }, + props: { + material: { required: true, type: Object }, + }, + data() { + return { + showBilling: Config.billingMode !== 'none', + }; + }, + computed: { + createDate() { + const { created_at: createdAt } = this.material; + return createdAt ? moment(createdAt).format('L') : null; + }, + updateDate() { + const { updated_at: updatedAt } = this.material; + return updatedAt ? moment(updatedAt).format('L') : null; + }, + categoryName() { + const { category_id: categoryId } = this.material; + const categoryNameGetter = store.getters['categories/categoryName']; + return categoryNameGetter(categoryId); + }, + subCategoryName() { + const { sub_category_id: subCategoryId } = this.material; + const subCategoryNameGetter = store.getters['categories/subCategoryName']; + return subCategoryNameGetter(subCategoryId); + }, + rentalPrice() { + const { rental_price: rentalPrice } = this.material; + return rentalPrice ? formatAmount(rentalPrice) : null; + }, + replacementPrice() { + const { replacement_price: replacementPrice } = this.material; + return replacementPrice ? formatAmount(replacementPrice) : null; + }, + queryStringCategory() { + return `category=${this.material.category_id}`; + }, + queryStringSubCategory() { + return `category=${this.material.category_id}&subCategory=${this.material.sub_category_id}`; + }, + }, +}; diff --git a/client/src/pages/MaterialView/MaterialView.scss b/client/src/pages/MaterialView/MaterialView.scss new file mode 100644 index 000000000..5008bdf89 --- /dev/null +++ b/client/src/pages/MaterialView/MaterialView.scss @@ -0,0 +1,8 @@ +.MaterialView { + color: $text-base-color; + padding-bottom: 6rem; + + .vue-tablist { + margin-bottom: 1rem; + } +} diff --git a/client/src/pages/MaterialView/MaterialView.vue b/client/src/pages/MaterialView/MaterialView.vue new file mode 100644 index 000000000..c9d1064b4 --- /dev/null +++ b/client/src/pages/MaterialView/MaterialView.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/client/src/pages/MaterialView/index.js b/client/src/pages/MaterialView/index.js new file mode 100644 index 000000000..38ed5fe99 --- /dev/null +++ b/client/src/pages/MaterialView/index.js @@ -0,0 +1,66 @@ +import { Tabs, Tab } from 'vue-slim-tabs'; +import store from '@/store'; +import Help from '@/components/Help/Help.vue'; +import Infos from './Infos/Infos.vue'; + +export default { + name: 'MaterialView', + components: { + Tabs, + Tab, + Help, + Infos, + }, + data() { + return { + help: '', + error: null, + isLoading: false, + material: { + id: this.$route.params.id, + attributes: [], + }, + }; + }, + mounted() { + store.dispatch('categories/fetch'); + + this.fetchMaterial(); + }, + methods: { + fetchMaterial() { + const { id } = this.material; + + this.resetHelpLoading(); + + const { resource } = this.$route.meta; + this.$http.get(`${resource}/${id}`) + .then(({ data }) => { + this.setMaterialData(data); + this.isLoading = false; + }) + .catch(this.displayError); + }, + + resetHelpLoading() { + this.error = null; + this.isLoading = true; + }, + + displayError(error) { + console.log(error); + this.error = error; + this.isLoading = false; + + const { code, details } = error.response?.data?.error || { code: 0, details: {} }; + if (code === 400) { + this.errors = { ...details }; + } + }, + + setMaterialData(data) { + this.material = data; + store.commit('setPageSubTitle', this.material.name); + }, + }, +}; diff --git a/client/src/pages/Materials/Materials.scss b/client/src/pages/Materials/Materials.scss index 7539a7574..b4387338c 100644 --- a/client/src/pages/Materials/Materials.scss +++ b/client/src/pages/Materials/Materials.scss @@ -40,22 +40,15 @@ } } - &__tag-item, &__add-tags { + flex: 1; + margin-bottom: .35rem; padding: .4rem .55rem; font-size: 0.85rem; } - &__tag-item { - flex: 0 0 auto; - margin: 0 0 .35rem .35rem; - background: $bg-color-emphasis; - border-radius: 10px; - } - - &__add-tags { - flex: 1; - margin-bottom: .35rem; + &__actions { + min-width: 110px; } } diff --git a/client/src/pages/Materials/Materials.vue b/client/src/pages/Materials/Materials.vue index 789342f41..20c8ce1dd 100644 --- a/client/src/pages/Materials/Materials.vue +++ b/client/src/pages/Materials/Materials.vue @@ -9,7 +9,7 @@ />
- @@ -55,14 +55,9 @@ role="button" @click="setTags(material.row)" > - - - {{ tag.name }} - +
+ + + arguments = $arguments; } + public function assert($input) + { + $result = $this->_validate($input); + if ($result instanceof NestedValidationException) { + throw $result; + } + + if ($result) { + return true; + } + + throw $this->reportError($input); + } + public function validate($input) + { + return $this->_validate($input) === true; + } + + // ------------------------------------------------------ + // - + // - Internal methods + // - + // ------------------------------------------------------ + + protected function _validate($input) { $params = $this->arguments; array_unshift($params, $input); @@ -33,6 +60,16 @@ public function validate($input) return $result; } + if ($result instanceof Validator) { + try { + $result->setName($this->name)->assert($input); + } catch (NestedValidationException $e) { + $this->setTemplate($e->getFullMessage()); + return $e; + } + return true; + } + $this->setTemplate($result); return false; } diff --git a/server/tests/endpoints/BillsTest.php b/server/tests/endpoints/BillsTest.php index 0de1a2e54..010dde523 100644 --- a/server/tests/endpoints/BillsTest.php +++ b/server/tests/endpoints/BillsTest.php @@ -1,9 +1,6 @@ client->post('/api/events/2/bill'); $this->assertStatusCode(SUCCESS_CREATED); - $newBillNumber = sprintf('%s-00002', date('Y')); + $newBillNumber = sprintf('%s-00001', date('Y')); $this->assertResponseData([ 'id' => 2, 'number' => $newBillNumber, @@ -114,7 +111,7 @@ public function testCreateBillWithDiscount() { $this->client->post('/api/events/2/bill', ['discountRate' => 50.0]); $this->assertStatusCode(SUCCESS_CREATED); - $newBillNumber = sprintf('%s-00002', date('Y')); + $newBillNumber = sprintf('%s-00001', date('Y')); $this->assertResponseData([ 'id' => 2, 'number' => $newBillNumber, diff --git a/server/tests/models/BillTest.php b/server/tests/models/BillTest.php index f0125e9cf..e2d4f1eb0 100644 --- a/server/tests/models/BillTest.php +++ b/server/tests/models/BillTest.php @@ -5,8 +5,6 @@ use Robert2\API\Models; use Robert2\API\Errors; -use Robert2\API\I18n\I18n; -use Robert2\API\Config\Config; final class BillTest extends ModelTestCase { @@ -159,7 +157,7 @@ public function testCreateFromEventNotFound() public function testCreateFromEvent() { $result = $this->model->createFromEvent(2, 1, 25.9542); - $newBillNumber = sprintf('%s-00002', date('Y')); + $newBillNumber = sprintf('%s-00001', date('Y')); $expected = [ 'id' => 2, 'number' => $newBillNumber, @@ -231,6 +229,6 @@ public function testDeleteByNumber() public function testGetLastBillNumber() { $result = $this->model->getLastBillNumber(); - $this->assertEquals(1, $result); + $this->assertEquals(0, $result); } }