This commit is contained in:
2025-03-31 20:17:05 +02:00
parent a03df0b268
commit d4d4c0c09d
1617 changed files with 1106381 additions and 268 deletions

View File

@@ -0,0 +1,65 @@
<!--**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*-->
<template>
<section class="stock-overview">
<ProductsActions />
<ProductsTable
:is-loading="isLoading"
@sort="sort"
/>
</section>
</template>
<script>
import ProductsActions from './products-actions';
import ProductsTable from './products-table';
const DEFAULT_SORT = 'asc';
export default {
computed: {
isLoading() {
return this.$store.state.isLoading;
},
},
methods: {
sort(sortDirection) {
this.$emit('fetch', sortDirection);
},
},
mounted() {
this.$store.dispatch('updatePageIndex', 1);
this.$store.dispatch('updateKeywords', []);
this.$store.dispatch('updateOrder', 'product');
this.$store.dispatch('isLoading');
this.$emit('resetFilters');
this.$emit('fetch', DEFAULT_SORT);
},
components: {
ProductsActions,
ProductsTable,
},
};
</script>

View File

@@ -0,0 +1,75 @@
<!--**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*-->
<template>
<div class="col-md-4">
<div class="movements">
<PSButton
type="button"
class="update-qty float-sm-right"
:class="classObject"
:disabled="disabled"
:primary="true"
@click="sendQty"
>
<i class="material-icons">edit</i>
{{ trans('button_movement_type') }}
</PSButton>
</div>
</div>
</template>
<script>
import PSButton from '@app/widgets/ps-button';
export default {
computed: {
disabled() {
return !this.$store.state.hasQty;
},
classObject() {
return {
'btn-primary': !this.disabled,
};
},
},
methods: {
sendQty() {
this.$store.dispatch('updateQtyByProductsId');
},
},
components: {
PSButton,
},
};
</script>
<style lang="scss" scoped>
@import '~@scss/config/_settings.scss';
.update-qty {
color: white;
transition: background-color 0.2s ease;
}
</style>

View File

@@ -0,0 +1,213 @@
<!--**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*-->
<template>
<tr :class="{'low-stock':lowStock}">
<td>
<div class="d-flex align-items-center">
<PSCheckbox
:id="id"
:ref="id"
:model="product"
@checked="productChecked"
/>
<PSMedia
class="d-flex align-items-center ml-2"
:thumbnail="thumbnail"
>
<p>
{{ product.product_name }}
<small v-if="hasCombination"><br>
{{ product.combination_name }}
</small>
</p>
</PSMedia>
</div>
</td>
<td>
{{ reference }}
</td>
<td>
{{ product.supplier_name }}
</td>
<td
v-if="product.active"
class="text-sm-center"
>
<i class="material-icons enable">check</i>
</td>
<td
v-else
class="text-sm-center"
>
<i class="material-icons disable">close</i>
</td>
<td
class="text-sm-center"
:class="{'stock-warning':lowStock}"
>
{{ physical }}
<span
v-if="updatedQty"
class="qty-update"
:class="{'stock-warning':lowStock}"
>
<i class="material-icons">trending_flat</i>
{{ physicalQtyUpdated }}
</span>
</td>
<td
class="text-sm-center"
:class="{'stock-warning':lowStock}"
>
{{ product.product_reserved_quantity }}
</td>
<td
class="text-sm-center"
:class="{'stock-warning':lowStock}"
>
{{ product.product_available_quantity }}
<span
v-if="updatedQty"
class="qty-update"
:class="{'stock-warning':lowStock}"
>
<i class="material-icons">trending_flat</i>
{{ availableQtyUpdated }}
</span>
<span
v-if="lowStock"
class="stock-warning ico ml-2"
data-toggle="pstooltip"
data-placement="top"
data-html="true"
:title="lowStockLevel"
>!</span>
</td>
<td class="qty-spinner">
<Spinner
:product="product"
@updateProductQty="updateProductQty"
/>
</td>
</tr>
</template>
<script>
import PSCheckbox from '@app/widgets/ps-checkbox';
import PSMedia from '@app/widgets/ps-media';
import ProductDesc from '@app/pages/stock/mixins/product-desc';
import {EventBus} from '@app/utils/event-bus';
import Spinner from '@app/pages/stock/components/overview/spinner';
export default {
props: {
product: {
type: Object,
required: true,
},
},
mixins: [ProductDesc],
computed: {
reference() {
if (this.product.combination_reference !== 'N/A') {
return this.product.combination_reference;
}
return this.product.product_reference;
},
updatedQty() {
return !!this.product.qty;
},
physicalQtyUpdated() {
return Number(this.physical) + Number(this.product.qty);
},
availableQtyUpdated() {
return Number(this.product.product_available_quantity) + Number(this.product.qty);
},
physical() {
const productAvailableQty = Number(this.product.product_available_quantity);
const productReservedQty = Number(this.product.product_reserved_quantity);
return productAvailableQty + productReservedQty;
},
lowStock() {
return this.product.product_low_stock_alert;
},
lowStockLevel() {
return `<div class="text-sm-left">
<p>${this.trans('product_low_stock')}</p>
<p><strong>${this.trans('product_low_stock_level')} ${this.product.product_low_stock_threshold}</strong></p>
</div>`;
},
lowStockAlert() {
return `<div class="text-sm-left">
<p><strong>${this.trans('product_low_stock_alert')} ${this.product.product_low_stock_alert}</strong></p>
</div>`;
},
id() {
return `product-${this.product.product_id}${this.product.combination_id}`;
},
},
methods: {
productChecked(checkbox) {
if (checkbox.checked) {
this.$store.dispatch('addSelectedProduct', checkbox.item);
} else {
this.$store.dispatch('removeSelectedProduct', checkbox.item);
}
},
updateProductQty(productToUpdate) {
const updatedProduct = {
product_id: productToUpdate.product.product_id,
combination_id: productToUpdate.product.combination_id,
delta: productToUpdate.delta,
};
this.$store.dispatch('updateProductQty', updatedProduct);
if (productToUpdate.delta) {
this.$store.dispatch('addProductToUpdate', updatedProduct);
} else {
this.$store.dispatch('removeProductToUpdate', updatedProduct);
}
},
},
mounted() {
EventBus.$on('toggleProductsCheck', (checked) => {
const ref = this.id;
if (this.$refs[ref]) {
this.$refs[ref].checked = checked;
}
});
$('[data-toggle="pstooltip"]').pstooltip();
},
data: () => ({
bulkEdition: false,
}),
components: {
Spinner,
PSMedia,
PSCheckbox,
},
};
</script>

View File

@@ -0,0 +1,148 @@
<!--**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*-->
<template>
<div class="row product-actions">
<div
class="col-md-8 qty d-flex align-items-center"
:class="{'active' : isFocused}"
>
<PSCheckbox
id="bulk-action"
ref="bulk-action"
class="mt-3"
:is-indeterminate="isIndeterminate()"
@checked="bulkChecked"
/>
<div class="ml-2">
<small>{{ trans('title_bulk') }}</small>
<PSNumber
class="bulk-qty"
:danger="danger"
:value="bulkEditQty"
:buttons="this.isFocused"
@focus="focusIn"
@blur="focusOut($event)"
@change="onChange"
@keyup="onKeyUp"
/>
</div>
</div>
<div class="col-md-4">
<PSButton
type="button"
class="update-qty float-sm-right my-4 mr-2"
:class="{'btn-primary': disabled }"
:disabled="disabled"
:primary="true"
@click="sendQty"
>
<i class="material-icons">edit</i>
{{ trans('button_movement_type') }}
</PSButton>
</div>
</div>
</template>
<script>
import PSNumber from '@app/widgets/ps-number';
import PSCheckbox from '@app/widgets/ps-checkbox';
import PSButton from '@app/widgets/ps-button';
import {EventBus} from '@app/utils/event-bus';
export default {
computed: {
disabled() {
return !this.$store.state.hasQty;
},
bulkEditQty() {
return this.$store.state.bulkEditQty;
},
selectedProductsLng() {
return this.$store.getters.selectedProductsLng;
},
},
watch: {
selectedProductsLng(value) {
if (value === 0 && this.$refs['bulk-action']) {
this.$refs['bulk-action'].checked = false;
this.isFocused = false;
}
if (value === 1 && this.$refs['bulk-action']) {
this.isFocused = true;
}
},
},
methods: {
isIndeterminate() {
const {selectedProductsLng} = this;
const productsLng = this.$store.state.products.length;
const isIndeterminate = (selectedProductsLng > 0 && selectedProductsLng < productsLng);
if (isIndeterminate) {
this.$refs['bulk-action'].checked = true;
}
return isIndeterminate;
},
focusIn() {
this.danger = !this.selectedProductsLng;
this.isFocused = !this.danger;
if (this.danger) {
EventBus.$emit('displayBulkAlert', 'error');
}
},
focusOut(event) {
this.isFocused = $(event.target).hasClass('ps-number');
this.danger = false;
},
bulkChecked(checkbox) {
if (!checkbox.checked) {
this.$store.dispatch('updateBulkEditQty', null);
}
if (!this.isIndeterminate()) {
EventBus.$emit('toggleProductsCheck', checkbox.checked);
}
},
sendQty() {
this.$store.dispatch('updateQtyByProductsId');
},
onChange(value) {
this.$store.dispatch('updateBulkEditQty', value);
},
onKeyUp(event) {
this.isFocused = true;
this.$store.dispatch('updateBulkEditQty', event.target.value);
},
},
data: () => ({
isFocused: false,
danger: false,
}),
components: {
PSNumber,
PSCheckbox,
PSButton,
},
};
</script>

View File

@@ -0,0 +1,165 @@
<!--**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*-->
<template>
<PSTable class="mt-1">
<thead>
<tr class="column-headers">
<th
scope="col"
width="27%"
class="product-title"
>
<PSSort
order="product"
@sort="sort"
:current-sort="currentSort"
>
{{ trans('title_product') }}
</PSSort>
</th>
<th scope="col">
<PSSort
order="reference"
@sort="sort"
:current-sort="currentSort"
>
{{ trans('title_reference') }}
</PSSort>
</th>
<th>
<PSSort
order="supplier"
@sort="sort"
:current-sort="currentSort"
>
{{ trans('title_supplier') }}
</PSSort>
</th>
<th class="text-center">
{{ trans('title_status') }}
</th>
<th class="text-center">
<PSSort
order="physical_quantity"
@sort="sort"
:current-sort="currentSort"
>
{{ trans('title_physical') }}
</PSSort>
</th>
<th class="text-center">
{{ trans('title_reserved') }}
</th>
<th class="text-center">
<PSSort
order="available_quantity"
@sort="sort"
:current-sort="currentSort"
>
{{ trans('title_available') }}
</PSSort>
</th>
<th :title="trans('title_edit_quantity')">
<i class="material-icons">edit</i>
{{ trans('title_edit_quantity') }}
</th>
</tr>
</thead>
<tbody>
<tr v-if="this.isLoading">
<td colspan="8">
<PSLoader
v-for="(n, index) in 3"
class="mt-1"
:key="index"
>
<div class="background-masker header-top" />
<div class="background-masker header-left" />
<div class="background-masker header-bottom" />
<div class="background-masker subheader-left" />
<div class="background-masker subheader-bottom" />
</PSLoader>
</td>
</tr>
<tr v-else-if="emptyProducts">
<td colspan="8">
<PSAlert
alert-type="ALERT_TYPE_WARNING"
:has-close="false"
>
{{ trans('no_product') }}
</PSAlert>
</td>
</tr>
<ProductLine
v-else
v-for="(product, index) in products"
:key="index"
:product="product"
/>
</tbody>
</PSTable>
</template>
<script>
import PSAlert from '@app/widgets/ps-alert';
import PSTable from '@app/widgets/ps-table/ps-table';
import PSSort from '@app/widgets/ps-table/ps-sort';
import PSLoader from '@app/widgets/ps-loader';
import ProductLine from './product-line';
export default {
props: {
isLoading: {
type: Boolean,
required: true,
},
},
components: {
ProductLine,
PSSort,
PSAlert,
PSTable,
PSLoader,
},
methods: {
sort(order, sortDirection) {
this.$store.dispatch('updateOrder', order);
this.$emit('sort', sortDirection === 'desc' ? 'desc' : 'asc');
},
},
computed: {
products() {
return this.$store.state.products;
},
emptyProducts() {
return !this.$store.state.products.length;
},
currentSort() {
return this.$store.state.order;
},
},
};
</script>

View File

@@ -0,0 +1,171 @@
<!--**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*-->
<template>
<form
class="qty"
:class="classObject"
@mouseover="focusIn"
@mouseleave="focusOut($event)"
@submit.prevent="sendQty"
>
<PSNumber
name="qty"
class="edit-qty"
placeholder="0"
pattern="\d*"
step="1"
:buttons="true"
:hover-buttons="true"
:value="getQuantity()"
@change="onChange"
@keyup="onKeyup($event)"
@focus="focusIn"
@blur="focusOut($event)"
/>
<transition name="fade">
<button
v-if="isActive"
class="check-button"
>
<i class="material-icons">check</i>
</button>
</transition>
</form>
</template>
<script>
import PSNumber from '@app/widgets/ps-number';
const {$} = window;
export default {
props: {
product: {
type: Object,
required: true,
},
},
computed: {
id() {
return `qty-${this.product.product_id}-${this.product.combination_id}`;
},
classObject() {
return {
active: this.isActive,
disabled: !this.isEnabled,
};
},
},
methods: {
getQuantity() {
if (!this.product.qty) {
this.isEnabled = false;
this.value = '';
}
return parseInt(this.value, 10);
},
onChange(val) {
this.value = val;
this.isEnabled = !!val;
},
deActivate() {
this.isActive = false;
this.isEnabled = false;
this.value = null;
this.product.qty = null;
},
onKeyup(event) {
const val = event.target.value;
if (val === 0) {
this.deActivate();
} else {
this.isActive = true;
this.isEnabled = true;
this.value = val;
}
},
focusIn() {
this.isActive = true;
},
focusOut(event) {
const value = parseInt(this.value, 10);
if (
!$(event.target).hasClass('ps-number')
&& (Number.isNaN(value) || value === 0)
) {
this.isActive = false;
}
this.isEnabled = !!this.value;
},
sendQty() {
const postUrl = this.product.edit_url;
if (
parseInt(this.product.qty, 10) !== 0
&& !Number.isNaN(parseInt(this.value, 10))
) {
this.$store.dispatch('updateQtyByProductId', {
url: postUrl,
delta: this.value,
});
this.deActivate();
}
},
},
watch: {
value(val) {
this.$emit('updateProductQty', {
product: this.product,
delta: val,
});
},
},
components: {
PSNumber,
},
data: () => ({
value: null,
isActive: false,
isEnabled: false,
}),
};
</script>
<style lang="scss" type="text/scss" scoped>
@import "~jquery-ui-dist/jquery-ui.css";
* {
outline: none;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>