feat: combine multiple orderrequests into one order
This commit is contained in:
@@ -21,6 +21,7 @@ $additionalCSS = [
|
||||
...$additionalCSS,
|
||||
'plugins/daterangepicker/daterangepicker.css',
|
||||
'plugins/vue/tt-components/css/tt-table.css',
|
||||
'plugins/vue/tt-components/css/tt-tooltip.css',
|
||||
'plugins/vue/tt-components/css/tt-loader.css',
|
||||
'plugins/vue/tt-components/css/tt-position-manager.css',
|
||||
];
|
||||
|
||||
@@ -16,7 +16,7 @@ class WarehouseShippingNoteController extends TTCrud {
|
||||
['value' => 'cancelled', 'text' => 'Storniert', 'icon' => 'fas fa-ban text-danger'],
|
||||
['value' => 'on_hold', 'text' => 'In Wartestellung', 'icon' => 'fas fa-pause text-warning'],
|
||||
]]],
|
||||
['key' => 'type', 'text' => 'Typ', 'required' => true],
|
||||
['key' => 'type', 'text' => 'Typ', 'required' => false],
|
||||
['key' => 'deliveryAddressName', 'text' => 'L.-Adr. Name', 'required' => true],
|
||||
['key' => 'deliveryAddressLine', 'text' => 'L.-Adr.', 'required' => true],
|
||||
['key' => 'deliveryAddressPLZ', 'text' => 'L.-Adr. PLZ', 'required' => true],
|
||||
|
||||
@@ -48,6 +48,7 @@ $jsFiles = [
|
||||
"plugins/vue/tt-components/tt-checkbox.js",
|
||||
"plugins/vue/tt-components/tt-textarea.js",
|
||||
"plugins/vue/tt-components/tt-position-manager.js",
|
||||
"plugins/vue/tt-components/tt-tooltip.js",
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -373,23 +373,26 @@ Vue.component('warehouse-order-modal', {
|
||||
return;
|
||||
}
|
||||
|
||||
const orderRequest = JSON.parse(localStorage.getItem('WarehouseOrder_create'));
|
||||
if (!orderRequest) return;
|
||||
const orderRequests = JSON.parse(localStorage.getItem('WarehouseOrder_create'));
|
||||
if (!orderRequests) return;
|
||||
|
||||
const positions = JSON.parse(orderRequest.positions);
|
||||
this.order.positions = await Promise.all(positions.map(async p => {
|
||||
const distributor = (await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getArticleDistributorData`,
|
||||
{params: {articleId: p.articleId}})).data[0];
|
||||
return {
|
||||
article: p.articleId,
|
||||
amount: p.amount,
|
||||
buyPrice: distributor.purchasePrice,
|
||||
distributorId: distributor.id,
|
||||
distributorArticleNumber: distributor.externalArticleNumber,
|
||||
verwendung: `${p.hasOwnProperty('purpose') ? p.purpose : ''} [Bestellwunsch: #${orderRequest.id}]`,
|
||||
linkedOrderRequestId: orderRequest.id
|
||||
};
|
||||
}));
|
||||
for (const orderRequest of orderRequests) {
|
||||
const positions = JSON.parse(orderRequest.positions);
|
||||
const parsedPositions = await Promise.all(positions.map(async p => {
|
||||
const distributor = (await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getArticleDistributorData`,
|
||||
{params: {articleId: p.articleId}})).data[0];
|
||||
return {
|
||||
article: p.articleId,
|
||||
amount: p.amount,
|
||||
buyPrice: distributor.purchasePrice,
|
||||
distributorId: distributor.id,
|
||||
distributorArticleNumber: distributor.externalArticleNumber,
|
||||
verwendung: `${p.hasOwnProperty('purpose') ? p.purpose : ''} [Bestellwunsch: #${orderRequest.id}]`,
|
||||
linkedOrderRequestId: orderRequest.id
|
||||
};
|
||||
}));
|
||||
this.order.positions = [...this.order.positions, ...parsedPositions];
|
||||
}
|
||||
|
||||
localStorage.removeItem('WarehouseOrder_create');
|
||||
},
|
||||
|
||||
@@ -17,8 +17,8 @@ window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [
|
||||
condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.cancelled === 1,
|
||||
},
|
||||
{
|
||||
key: "createOrder",
|
||||
title: "Bestellung erstellen",
|
||||
key: "addOrderToCart",
|
||||
title: "Bestellung in den Warenkorb",
|
||||
class: "fas fa-shopping-cart text-primary", // Use shopping-cart to indicate order creation
|
||||
condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1'
|
||||
&& row.cancelled === 0 && (!row.linkedOrderIds || row.linkedOrderIds.length === 0)
|
||||
@@ -203,7 +203,18 @@ Vue.component('warehouse-order-request', {
|
||||
@undoneOrder="undoneOrder"
|
||||
@createLog="createLog"
|
||||
@createOrder="createOrder"
|
||||
@addOrderToCart="addOrderToCart"
|
||||
ref="crud">
|
||||
<template #table-top-buttons>
|
||||
<tt-tooltip :text="createOrdersButtonToolTipText">
|
||||
<tt-button
|
||||
:disabled="orderShoppingCart.length === 0"
|
||||
@click="createOrder"
|
||||
additional-class="btn-outline-success text-center"
|
||||
:text="createOrdersButtonText" icon="fas fa-shopping-cart"/>
|
||||
</tt-tooltip>
|
||||
</template>
|
||||
|
||||
<template #linkedorderids="{row}">
|
||||
<linked-order-status :linkedOrders="row.linkedOrderIds" v-if="row.linkedOrderIds"/>
|
||||
</template>
|
||||
@@ -229,7 +240,8 @@ Vue.component('warehouse-order-request', {
|
||||
addLogModalId: null,
|
||||
showHiddenRequests: false,
|
||||
showCanceledRequests: false,
|
||||
orderRequestModalId: null
|
||||
orderRequestModalId: null,
|
||||
orderShoppingCart: [],
|
||||
}),
|
||||
methods: {
|
||||
openHistory(e) {
|
||||
@@ -263,12 +275,26 @@ Vue.component('warehouse-order-request', {
|
||||
uncancelRequest(row) {
|
||||
this.cancelRequest(row, '0');
|
||||
},
|
||||
async createOrder(row) {
|
||||
const res = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrderRequest/getById?id=${row.id}`);
|
||||
async createOrder() {
|
||||
if (this.orderShoppingCart.length > 0) {
|
||||
localStorage.setItem('WarehouseOrder_create', JSON.stringify(this.orderShoppingCart));
|
||||
window.location.href = `${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder`;
|
||||
} else window.notify('warning', 'Warenkorb ist leer');
|
||||
},
|
||||
async addOrderToCart(row) {
|
||||
const res = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/getById?id=${row.id}`);
|
||||
if (res.data?.positions && typeof res.data.positions === 'string') {
|
||||
localStorage.setItem('WarehouseOrder_create', JSON.stringify(res.data));
|
||||
window.location.href = `${window.TT_CONFIG.BASE_PATH}/WarehouseOrder`;
|
||||
} else window.notify('error', res.data.message || 'Fehler beim erstellen der Bestellung');
|
||||
this.orderShoppingCart.push(res.data);
|
||||
window.notify('success', 'Bestellung in den Warenkorb gelegt');
|
||||
} else window.notify('error', res.data.message || 'Fehler beim hinzufügen der Bestellung');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
createOrdersButtonText: function () {
|
||||
return this.orderShoppingCart.length > 0 ? `${this.orderShoppingCart.length} Bestellung(en) erstellen` : 'Bestellung(en) erstellen';
|
||||
},
|
||||
createOrdersButtonToolTipText: function () {
|
||||
return this.orderShoppingCart.length > 0 ? `Erstellt ${this.orderShoppingCart.length} Bestellung(en)` : 'Keine Bestellung im Warenkorb';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
96
public/plugins/vue/tt-components/css/tt-tooltip.css
Normal file
96
public/plugins/vue/tt-components/css/tt-tooltip.css
Normal file
@@ -0,0 +1,96 @@
|
||||
.tt-tooltip-wrapper {
|
||||
position: relative;
|
||||
display: inline-block; /* Or 'block' depending on your layout needs */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tt-tooltip-box {
|
||||
position: absolute;
|
||||
background-color: #333; /* Dark background */
|
||||
color: #fff; /* White text */
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
z-index: 10; /* Ensure it's above other elements */
|
||||
opacity: 0; /* Hidden by default, fade in */
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none; /* Prevent tooltip from interfering with mouse events */
|
||||
text-align: center; /* Center text */
|
||||
}
|
||||
|
||||
/* Make tooltip visible when showTooltip is true */
|
||||
.tt-tooltip-wrapper:hover .tt-tooltip-box {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
/* Positioning */
|
||||
.tt-tooltip-top {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-bottom: 5px; /* Space between element and tooltip */
|
||||
}
|
||||
|
||||
.tt-tooltip-bottom {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.tt-tooltip-left {
|
||||
right: 100%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tt-tooltip-right {
|
||||
left: 100%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Optional: Add arrows */
|
||||
.tt-tooltip-box::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.tt-tooltip-top::after {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-color: #333 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.tt-tooltip-bottom::after {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-color: transparent transparent #333 transparent;
|
||||
}
|
||||
|
||||
.tt-tooltip-left::after {
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
margin-top: -5px;
|
||||
border-color: transparent transparent transparent #333;
|
||||
}
|
||||
|
||||
.tt-tooltip-right::after {
|
||||
top: 50%;
|
||||
right: 100%;
|
||||
margin-top: -5px;
|
||||
border-color: transparent #333 transparent transparent;
|
||||
}
|
||||
|
||||
.tt-tooltip-wrapper > * {
|
||||
display: inline-block; /* Ensure the tooltip wrapper behaves correctly */
|
||||
width: 100% !important;
|
||||
}
|
||||
@@ -2,9 +2,7 @@
|
||||
Vue.component('tt-button', {
|
||||
//language=Vue
|
||||
template: `
|
||||
<div>
|
||||
<template v-if="href">
|
||||
<a :href="href" class="btn" :class="buttonClasses" @click="handleClick">
|
||||
<a v-if="href" :href="href" class="btn" :class="buttonClasses" @click="handleClick">
|
||||
<template v-if="loading">
|
||||
<span class="spinner"></span>
|
||||
</template>
|
||||
@@ -13,9 +11,7 @@ Vue.component('tt-button', {
|
||||
{{text}}
|
||||
</template>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button @click="handleClick" class="btn" :class="buttonClasses" :disabled="loading">
|
||||
<button v-else @click="handleClick" class="btn" :class="buttonClasses" :disabled="loading">
|
||||
<template v-if="loading">
|
||||
<span class="spinner"></span>
|
||||
</template>
|
||||
@@ -24,8 +20,6 @@ Vue.component('tt-button', {
|
||||
{{text}}
|
||||
</template>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
sm: {type: Boolean, default: false},
|
||||
|
||||
@@ -65,7 +65,7 @@ Vue.component('tt-table-crud', {
|
||||
<tt-input v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'number'" v-model="crudModalData[column.key]" :label="column.text" type="number" sm row/>
|
||||
<tt-textarea v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'textarea'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
|
||||
<tt-select v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'select'" v-model="crudModalData[column.key]" :label="column.text" :options="column.items" sm row/>
|
||||
<tt-autocomplete v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'autocomplete'" v-model="crudModalData[column.key]" :label="column.text" :api-url="column.apiUrl" :items="column.items" sm row :return-text="column.returnText" />
|
||||
<tt-autocomplete v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'autocomplete'" v-model="crudModalData[column.key]" :label="column.text" :api-url="column.apiUrl" :items="typeof column.items === 'string' ? [] : column.items" sm row :return-text="column.returnText" />
|
||||
<tt-date-picker v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'datepicker'" v-model="crudModalData[column.key]" :label="column.text" sm row :date-range="false" :ref="column.key.toLowerCase() + '-modal-input'"/>
|
||||
<tt-icon-select v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'icon-select'" v-model="crudModalData[column.key]" :options="column.items" :label="column.text" sm row/>
|
||||
<tt-checkbox v-show="crudModalColumnVisibility[column.key]" v-else-if="column.type === 'checkbox'" v-model="crudModalData[column.key]" :label="column.text" sm row/>
|
||||
|
||||
31
public/plugins/vue/tt-components/tt-tooltip.js
Normal file
31
public/plugins/vue/tt-components/tt-tooltip.js
Normal file
@@ -0,0 +1,31 @@
|
||||
Vue.component('tt-tooltip', {
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: 'Tooltip text'
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
default: 'top', // Options: top, bottom, left, right
|
||||
validator: function (value) {
|
||||
// The value must match one of these strings
|
||||
return ['top', 'bottom', 'left', 'right'].indexOf(value) !== -1;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showTooltip: true
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<div class="tt-tooltip-wrapper"
|
||||
@mouseenter="showTooltip = true"
|
||||
@mouseleave="showTooltip = false">
|
||||
<slot></slot> <div v-if="showTooltip" class="tt-tooltip-box" :class="['tt-tooltip-' + position]">
|
||||
{{ text }}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
Reference in New Issue
Block a user