201 lines
9.9 KiB
JavaScript
201 lines
9.9 KiB
JavaScript
async function handleApiResponse(responsePromise) {
|
|
const res = await responsePromise;
|
|
if (!res.data.success) {
|
|
const errors = res.data.errors;
|
|
const errorMessage = Array.isArray(errors) ? errors.join(', ') : Object.values(errors).join(', ');
|
|
return window.notify('error', `Fehler: ${errorMessage}`);
|
|
}
|
|
window.notify('success', res.data.message || 'Erfolgreich');
|
|
window.dispatchEvent(new Event('refreshTable'));
|
|
}
|
|
|
|
Vue.component('warehouse-article-prices', {
|
|
props: {id: {type: Number, required: true}},
|
|
template: `
|
|
<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;price.pendingChanges = true"/>
|
|
<tt-input sm v-model="price.priceOverride" label="Preis" @input="price.priceMultiplier = null;price.pendingChanges = true"/>
|
|
<div style="align-self: end;display: flex;align-items: center;">
|
|
<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)"/>
|
|
<i v-if="price.pendingChanges" class="fa-solid fa-triangle-exclamation ml-1 text-warning" style="font-size: 28px" title="Dieser Preis wurde noch nicht gespeichert"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</tt-card>
|
|
`,
|
|
data: () => ({window, articlePrices: {}}),
|
|
async mounted() {
|
|
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,
|
|
pendingChanges: 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 ? parseFloat(price.priceMultiplier.toString().replace(',', '.')) : null,
|
|
priceOverride: price.priceOverride ? parseFloat(price.priceOverride.toString().replace(',', '.')) : null
|
|
};
|
|
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();
|
|
}
|
|
}
|
|
});
|
|
|
|
// 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" @input="distributor.pendingChanges = true"/>
|
|
<tt-input sm v-model="distributor.purchasePrice" label="Einkaufspreis" @input="distributor.pendingChanges = true"/>
|
|
<div style="align-self: end; display: flex;align-items: center;">
|
|
<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.id)"/>
|
|
<i v-if="distributor.pendingChanges || !distributor.id" class="fa-solid fa-triangle-exclamation ml-1 text-warning"
|
|
style="font-size: 28px" title="Dieser Preis wurde noch nicht gespeichert"></i>
|
|
|
|
</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) {
|
|
delete distributor.pendingChanges;
|
|
distributor.purchasePrice = distributor.purchasePrice ? parseFloat(distributor.purchasePrice.toString().replace(',', '.')) : null;
|
|
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/${distributor.id ? 'update' : 'create'}`, distributor));
|
|
await this.fetchArticleDistributors();
|
|
},
|
|
async deleteDistributor(distributorId) {
|
|
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/delete`, {id: distributorId, articleId: this.id}));
|
|
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;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// 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 style="white-space:nowrap;" v-if="price && window.TT_CONFIG['WAREHOUSE_ADMIN']">{{ price.title }}: <span
|
|
>{{ price.price }} €</span><br></span>
|
|
<span v-else-if="price && price.title === 'Verkauf'">{{ price.price }} €</span>
|
|
</template>
|
|
</template>
|
|
|
|
<template v-slot:description="{ row }">
|
|
<tt-tooltip
|
|
allow-wrapping
|
|
v-if="row.description.length > 45"
|
|
:text="row.description" position="top">
|
|
<span v-if="row.description.length > 45">{{ row.description.substring(0, 45) }}...</span>
|
|
</tt-tooltip>
|
|
<span v-else>{{ row.description }}</span>
|
|
</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, articleTest: null}),
|
|
mounted() {
|
|
const table = this.$refs.table?.$refs?.table;
|
|
if (!table) return;
|
|
const showId = new URLSearchParams(window.location.search).get('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();
|
|
}
|
|
|
|
window.addEventListener('refreshTable', () => {
|
|
table.refreshTable();
|
|
});
|
|
}
|
|
}); |