new update
This commit is contained in:
@@ -1,5 +1,38 @@
|
||||
.warehouse-article-prices > div {
|
||||
/* style.css */
|
||||
.warehouse-article-prices > div,
|
||||
.warehouse-article-distributor > div {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(120px, 1fr)) 72px;
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(3, minmax(150px, 1fr)) 120px; /* Defines 4 columns */
|
||||
gap: 12px; /* Use gap for spacing */
|
||||
margin-bottom: 8px; /* Add small gap between rows */
|
||||
}
|
||||
|
||||
.warehouse-article-prices .form-group,
|
||||
.warehouse-article-distributor .form-group {
|
||||
margin-bottom: 0; /* Keep inputs tight in grid cells */
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.warehouse-article-prices > div,
|
||||
.warehouse-article-distributor > div {
|
||||
display: block; /* Stack items vertically */
|
||||
border: 1px solid #eee; /* Lighter border */
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.warehouse-article-prices > div > *,
|
||||
.warehouse-article-distributor > div > * {
|
||||
display: block; /* Ensure children are block */
|
||||
margin-bottom: 10px !important; /* Add space between stacked items */
|
||||
}
|
||||
.warehouse-article-prices > div > div:last-child, /* Target button container */
|
||||
.warehouse-article-distributor > div > div:last-child {
|
||||
text-align: right; /* Keep buttons aligned right */
|
||||
margin-bottom: 0 !important; /* Remove extra margin below buttons */
|
||||
}
|
||||
.warehouse-article-prices > div > div:last-child > *, /* Target buttons inside */
|
||||
.warehouse-article-distributor > div > div:last-child > * {
|
||||
margin-left: 5px; /* Add small space between buttons */
|
||||
}
|
||||
}
|
||||
@@ -1,97 +1,175 @@
|
||||
async function handleApiResponse(responsePromise) {
|
||||
const res = await responsePromise;
|
||||
if (res.data.success === false) return window.notify('error', `Fehler: ${res.data.errors.join(', ')}`);
|
||||
window.notify('success', res.data.message || 'Erfolgreich');
|
||||
}
|
||||
|
||||
Vue.component('warehouse-article-prices', {
|
||||
props: {id: {type: Number, required: true}},
|
||||
template: `
|
||||
<div>
|
||||
|
||||
<tt-card>
|
||||
<h4>Artikelpreise</h4>
|
||||
|
||||
<div class="warehouse-article-prices">
|
||||
<div v-for="(price, index) in articlePrices" :key="index" class="article-price">
|
||||
<div class="article-price-type">
|
||||
<span v-if="price.isRobot" class="robot-icon"><i class="fa-solid fa-robot"></i></span>
|
||||
{{ index }}
|
||||
</div>
|
||||
<tt-input sm v-model="price.priceMultiplier" label="Preisfaktor"/>
|
||||
<tt-input sm v-model="price.priceOverride" label="Preisoverride"/>
|
||||
|
||||
<div>
|
||||
<tt-button sm icon="fa-solid fa-trash" class="remove-price" additional-class="btn-danger"></tt-button>
|
||||
<tt-button sm icon="fa-solid fa-save" class="save-price" additional-class="btn-primary"></tt-button>
|
||||
</div>
|
||||
|
||||
<tt-card>
|
||||
<h4 style="text-align: center">Artikelpreise überschreiben</h4>
|
||||
<div class="warehouse-article-prices">
|
||||
<div v-for="(price, typeTitle) in articlePrices" :key="typeTitle">
|
||||
<div style="align-self: center;">
|
||||
<i v-if="price.isRobot" class="fa-solid fa-robot"></i> {{ typeTitle }}
|
||||
</div>
|
||||
<tt-input sm v-model="price.priceMultiplier" label="Preisfaktor" @input="price.priceOverride = null"/>
|
||||
<tt-input sm v-model="price.priceOverride" label="Preis" @input="price.priceMultiplier = null"/>
|
||||
<div style="align-self: end;">
|
||||
<tt-button sm icon="fa-solid fa-save" additional-class="btn-primary" @click="savePrice(price)"/>
|
||||
<tt-button sm icon="fa-solid fa-trash" additional-class="btn-danger" v-if="!price.isRobot"
|
||||
@click="deletePrice(price)"/>
|
||||
</div>
|
||||
</div>
|
||||
</tt-card>
|
||||
|
||||
</div>`,
|
||||
data: () => ({
|
||||
window,
|
||||
articlePrices: [],
|
||||
}),
|
||||
</div>
|
||||
</tt-card>
|
||||
`,
|
||||
data: () => ({window, articlePrices: {}}),
|
||||
async mounted() {
|
||||
const [res1, res2] = await Promise.all([
|
||||
axios.post(window['TT_CONFIG']['BASE_PATH'] + '/WarehouseArticlePrice/get', {filters: {articleId: this.id}}),
|
||||
axios.post(window['TT_CONFIG']['BASE_PATH'] + '/WarehouseArticlePriceType/get')
|
||||
]);
|
||||
const prices = {};
|
||||
res2.data.rows.forEach(t => prices[t.title] = {
|
||||
isRobot: true,
|
||||
articlePriceTypeId: t.id,
|
||||
priceMultiplier: t.defaultPriceFactor,
|
||||
priceOverride: null
|
||||
});
|
||||
res1.data.rows.forEach(p => {
|
||||
const t = res2.data.rows.find(t => t.id === p.articlePriceTypeId);
|
||||
if (t) prices[t.title] = {
|
||||
isRobot: false,
|
||||
articlePriceTypeId: p.articlePriceTypeId,
|
||||
priceMultiplier: p.priceMultiplier || t.defaultPriceFactor,
|
||||
priceOverride: p.priceOverride
|
||||
await this.fetchArticlePrices();
|
||||
},
|
||||
methods: {
|
||||
async fetchArticlePrices() {
|
||||
const [pricesRes, typesRes] = await Promise.all([
|
||||
axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/get`, {filters: {articleId: this.id}}),
|
||||
axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePriceType/get`)
|
||||
]);
|
||||
const prices = {};
|
||||
typesRes.data.rows.forEach(type => prices[type.title] = {
|
||||
isRobot: true,
|
||||
articlePriceTypeId: type.id,
|
||||
priceMultiplier: type.defaultPriceFactor,
|
||||
priceOverride: null
|
||||
});
|
||||
pricesRes.data.rows.forEach(pData => {
|
||||
const type = typesRes.data.rows.find(t => t.id === pData.articlePriceTypeId);
|
||||
if (type) prices[type.title] = {
|
||||
id: pData.id,
|
||||
isRobot: false,
|
||||
articlePriceTypeId: pData.articlePriceTypeId,
|
||||
priceMultiplier: pData.priceMultiplier,
|
||||
priceOverride: pData.priceOverride
|
||||
};
|
||||
});
|
||||
this.articlePrices = prices;
|
||||
},
|
||||
async savePrice(price) {
|
||||
const payload = {
|
||||
articleId: this.id,
|
||||
articlePriceTypeId: price.articlePriceTypeId,
|
||||
priceMultiplier: price.priceMultiplier,
|
||||
priceOverride: price.priceOverride
|
||||
};
|
||||
});
|
||||
this.articlePrices = prices;
|
||||
if (price.isRobot) {
|
||||
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/create`, payload));
|
||||
await this.fetchArticlePrices();
|
||||
} else {
|
||||
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/update`, {id: price.id, ...payload}));
|
||||
await this.fetchArticlePrices();
|
||||
}
|
||||
},
|
||||
async deletePrice(price) {
|
||||
const payload = {id: price.id, articleId: this.id, articlePriceTypeId: price.articlePriceTypeId}
|
||||
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/delete`, payload));
|
||||
await this.fetchArticlePrices();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Vue.component('warehouse-article', {
|
||||
//language=Vue
|
||||
// warehouse-article-distributor.vue.js
|
||||
Vue.component('warehouse-article-distributor', {
|
||||
props: {id: {type: Number, required: true}},
|
||||
template: `
|
||||
<tt-card>
|
||||
<h4 style="text-align: center">Lieferanten für diesen Artikel</h4>
|
||||
<tt-autocomplete :api-url="window['TT_CONFIG']['BASE_PATH'] + '/WarehouseDistributor/autocomplete'"
|
||||
v-model="newDistributorId" label="Neuen Lieferant hinzufügen"/>
|
||||
<div class="warehouse-article-distributor">
|
||||
<div v-for="(distributor, index) in articleDistributors" :key="distributor.id || ('new-' + index)">
|
||||
<tt-resolver style="align-self: center;" reference="WarehouseDistributor"
|
||||
:value="distributor.distributorId"></tt-resolver>
|
||||
<tt-input sm v-model="distributor.externalArticleNumber" label="Externe Artikelnummer"/>
|
||||
<tt-input sm v-model="distributor.purchasePrice" label="Einkaufspreis"/>
|
||||
<div style="align-self: end;">
|
||||
<tt-button sm icon="fa-solid fa-save" additional-class="btn-primary"
|
||||
@click="saveDistributor(distributor)"/>
|
||||
<tt-button sm icon="fa-solid fa-trash" additional-class="btn-danger" v-if="distributor.id"
|
||||
@click="deleteDistributor(distributor)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</tt-card>
|
||||
`,
|
||||
data: () => ({window, articleDistributors: [], newDistributorId: null}),
|
||||
async mounted() {
|
||||
await this.fetchArticleDistributors();
|
||||
},
|
||||
methods: {
|
||||
async fetchArticleDistributors() {
|
||||
const res = await axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/get`, {filters: {articleId: this.id}});
|
||||
this.articleDistributors = res.data.rows;
|
||||
},
|
||||
async saveDistributor(distributor) {
|
||||
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/${distributor.id ? 'update' : 'create'}`, distributor));
|
||||
await this.fetchArticleDistributors();
|
||||
},
|
||||
async deleteDistributor(distributor) {
|
||||
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/delete`, distributor));
|
||||
await this.fetchArticleDistributors();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
newDistributorId(newId) {
|
||||
if (newId) {
|
||||
if (!this.articleDistributors.some(d => d.distributorId === newId)) {
|
||||
this.articleDistributors.push({
|
||||
articleId: this.id,
|
||||
distributorId: newId,
|
||||
externalArticleNumber: null,
|
||||
purchasePrice: null
|
||||
});
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.newDistributorId = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
<tt-table-crud ref="table" @openHistory="historyModal = true; historyModalId = $event.id">
|
||||
|
||||
// warehouse-article.vue.js
|
||||
Vue.component('warehouse-article', {
|
||||
template: `
|
||||
<tt-card>
|
||||
<tt-table-crud ref="table" @openHistory="historyModalId = $event.id; historyModal = true">
|
||||
<template v-slot:cheapestsellprice="{ row }">
|
||||
<template v-for="price in JSON.parse(row.cheapestSellPrice)">
|
||||
<span v-if="price && window.TT_CONFIG['WAREHOUSE_ADMIN'] == true">{{price.title}}: <span
|
||||
style="white-space:nowrap;"> {{(price.price)}} €</span><br></span>
|
||||
<span v-else-if="price && price.title === 'Verkauf'">{{(price.price)}} €</span>
|
||||
<template v-for="price in JSON.parse(row.cheapestSellPrice || '[]')">
|
||||
<span v-if="price && window.TT_CONFIG['WAREHOUSE_ADMIN']">{{ price.title }}: <span
|
||||
style="white-space:nowrap;">{{ price.price }} €</span><br></span>
|
||||
<span v-else-if="price && price.title === 'Verkauf'">{{ price.price }} €</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<template v-slot:modal-prepend="{ crudModalData }">
|
||||
<warehouse-article-prices v-if="crudModalData.id" :id="crudModalData.id"/>
|
||||
<warehouse-article-distributor v-if="crudModalData.id" :id="crudModalData.id"/>
|
||||
</template>
|
||||
|
||||
</tt-table-crud>
|
||||
|
||||
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
|
||||
</tt-card>
|
||||
`, data: () => ({
|
||||
window,
|
||||
historyModal: false,
|
||||
historyModalId: null,
|
||||
}),
|
||||
`,
|
||||
data: () => ({window, historyModal: false, historyModalId: null}),
|
||||
mounted() {
|
||||
const table = this.$refs.table?.$refs?.table;
|
||||
if (!table) return;
|
||||
|
||||
const showId = new URLSearchParams(window.location.search).get('showId');
|
||||
const currentFilterId = table.filters?.id;
|
||||
|
||||
if ((showId && currentFilterId !== showId) || (!showId && currentFilterId)) {
|
||||
table.filters = showId ? {id: showId} : {};
|
||||
if (showId && (!table.filters || table.filters.id !== showId)) {
|
||||
table.filters = {...table.filters, id: showId};
|
||||
table.refreshTable();
|
||||
} else if (!showId && table.filters?.id) {
|
||||
delete table.filters.id;
|
||||
if (Object.keys(table.filters).length === 0) table.filters = {};
|
||||
table.refreshTable();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
Reference in New Issue
Block a user