Warehouse article/improve modal
This commit is contained in:
@@ -16,29 +16,55 @@ async function handleApiResponse(responsePromise) {
|
||||
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 class="wa-prices-section">
|
||||
<h5 class="wa-section-title"><i class="fas fa-tags mr-2"></i>Artikelpreise überschreiben</h5>
|
||||
<div class="wa-prices-grid-dense">
|
||||
<div v-for="(price, typeTitle) in articlePrices" :key="typeTitle" class="wa-price-item">
|
||||
<div class="wa-price-header">
|
||||
<i v-if="price.isRobot" class="fas fa-robot mr-1 text-muted" style="font-size: 0.75rem;"></i>
|
||||
<strong style="font-size: 0.8rem;">{{ typeTitle }}</strong>
|
||||
<i v-if="price.pendingChanges" class="fas fa-exclamation-triangle text-warning ml-auto"
|
||||
style="font-size: 0.75rem;" title="Nicht gespeichert"></i>
|
||||
</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 class="wa-price-body">
|
||||
<tt-input
|
||||
v-model="price.priceMultiplier"
|
||||
@input="handleFactorInput(price)"
|
||||
placeholder="Faktor"
|
||||
type="number"
|
||||
sm no-form-group/>
|
||||
<tt-input
|
||||
v-model="price.priceOverride"
|
||||
@input="handlePriceInput(price)"
|
||||
placeholder="Preis €"
|
||||
type="number"
|
||||
sm no-form-group/>
|
||||
<div class="wa-price-actions">
|
||||
<button class="btn btn-sm btn-primary" @click="savePrice(price)" title="Speichern">
|
||||
<i class="fas fa-save"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" @click="deletePrice(price)" :disabled="price.isRobot" title="Löschen">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</tt-card>
|
||||
</div>
|
||||
`,
|
||||
data: () => ({window, articlePrices: {}}),
|
||||
async mounted() {
|
||||
await this.fetchArticlePrices();
|
||||
},
|
||||
methods: {
|
||||
handleFactorInput(price) {
|
||||
if (price.priceMultiplier) price.priceOverride = null;
|
||||
price.pendingChanges = true;
|
||||
},
|
||||
handlePriceInput(price) {
|
||||
if (price.priceOverride) price.priceMultiplier = null;
|
||||
price.pendingChanges = true;
|
||||
},
|
||||
async fetchArticlePrices() {
|
||||
const [pricesRes, typesRes] = await Promise.all([
|
||||
axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/get`, {filters: {articleId: this.id}}),
|
||||
@@ -53,7 +79,8 @@ Vue.component('warehouse-article-prices', {
|
||||
});
|
||||
pricesRes.data.rows.forEach(pData => {
|
||||
const type = typesRes.data.rows.find(t => t.id === pData.articlePriceTypeId);
|
||||
if (type) prices[type.title] = {
|
||||
if (!type) return;
|
||||
prices[type.title] = {
|
||||
id: pData.id,
|
||||
isRobot: false,
|
||||
pendingChanges: false,
|
||||
@@ -71,13 +98,10 @@ Vue.component('warehouse-article-prices', {
|
||||
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();
|
||||
}
|
||||
const endpoint = price.isRobot ? 'create' : 'update';
|
||||
const data = price.isRobot ? payload : {id: price.id, ...payload};
|
||||
await this.window.handleApiResponse(axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticlePrice/${endpoint}`, data));
|
||||
await this.fetchArticlePrices();
|
||||
},
|
||||
async deletePrice(price) {
|
||||
const payload = {id: price.id, articleId: this.id, articlePriceTypeId: price.articlePriceTypeId}
|
||||
@@ -87,42 +111,187 @@ Vue.component('warehouse-article-prices', {
|
||||
}
|
||||
});
|
||||
|
||||
// warehouse-article-distributor.vue.js
|
||||
Vue.component('warehouse-article-distributor', {
|
||||
props: {id: {type: Number, required: true}},
|
||||
Vue.component('warehouse-distributor-directory-modal', {
|
||||
props: {
|
||||
show: { type: Boolean, default: false },
|
||||
allDistributors: { type: Array, default: () => [] },
|
||||
articleDistributors: { type: Array, default: () => [] }
|
||||
},
|
||||
data: () => ({
|
||||
distributorSearch: ''
|
||||
}),
|
||||
watch: {
|
||||
show(newVal) {
|
||||
if (newVal) document.documentElement.style.overflow = 'hidden';
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredDistributors() {
|
||||
if (!this.distributorSearch) return this.allDistributors;
|
||||
const search = this.distributorSearch.toLowerCase();
|
||||
return this.allDistributors.filter(d => d.name.toLowerCase().includes(search));
|
||||
},
|
||||
alphabetWithDistributors() {
|
||||
const letters = new Set();
|
||||
this.filteredDistributors.forEach(d => {
|
||||
const firstChar = d.name.charAt(0).toUpperCase();
|
||||
if (/[A-Z]/.test(firstChar)) letters.add(firstChar);
|
||||
});
|
||||
return Array.from(letters).sort();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDistributorsByLetter(letter) {
|
||||
return this.filteredDistributors.filter(d => d.name.charAt(0).toUpperCase() === letter);
|
||||
},
|
||||
isDistributorAdded(distributorId) {
|
||||
return this.articleDistributors.some(d => d.distributorId === distributorId);
|
||||
},
|
||||
selectDistributor(distributorId) {
|
||||
this.$emit('select', distributorId);
|
||||
this.$emit('close');
|
||||
},
|
||||
close() {
|
||||
this.$emit('close');
|
||||
}
|
||||
},
|
||||
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 v-if="show" class="modal fade show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5);" @click.self="close">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-truck mr-2"></i>
|
||||
Lieferant hinzufügen
|
||||
</h5>
|
||||
<button type="button" class="close" @click="close">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="wa-directory-header mb-3">
|
||||
<tt-input
|
||||
v-model="distributorSearch"
|
||||
placeholder="Lieferant suchen..."
|
||||
sm
|
||||
prefix-icon="fas fa-search"/>
|
||||
</div>
|
||||
<div class="wa-directory-list">
|
||||
<div v-for="letter in alphabetWithDistributors" :key="letter" class="wa-directory-group">
|
||||
<div class="wa-directory-letter">{{ letter }}</div>
|
||||
<div class="wa-directory-items">
|
||||
<button v-for="dist in getDistributorsByLetter(letter)"
|
||||
:key="dist.id"
|
||||
class="wa-directory-item"
|
||||
:class="{ 'active': isDistributorAdded(dist.id) }"
|
||||
@click="selectDistributor(dist.id)"
|
||||
:disabled="isDistributorAdded(dist.id)">
|
||||
{{ dist.name }}
|
||||
<i v-if="isDistributorAdded(dist.id)" class="fas fa-check ml-1"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</tt-card>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
Vue.component('warehouse-article-distributor', {
|
||||
props: {id: {type: Number, required: true}},
|
||||
template: `
|
||||
<div class="wa-distributors-section">
|
||||
<div class="wa-section-title-with-action">
|
||||
<h5 class="wa-section-title mb-0"><i class="fas fa-truck mr-2"></i>Lieferanten</h5>
|
||||
<button class="btn btn-sm btn-primary" @click="showDirectoryModal = true">
|
||||
<i class="fas fa-plus mr-1"></i>Lieferant hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<warehouse-distributor-directory-modal
|
||||
:show="showDirectoryModal"
|
||||
:all-distributors="allDistributors"
|
||||
:article-distributors="articleDistributors"
|
||||
@select="addDistributor"
|
||||
@close="showDirectoryModal = false"/>
|
||||
|
||||
<div v-if="articleDistributors.length === 0" class="text-muted text-center py-3" style="font-size: 0.85rem;">
|
||||
<i class="fas fa-info-circle mr-1"></i>Keine Lieferanten zugewiesen
|
||||
</div>
|
||||
|
||||
<div v-else class="wa-distributors-grid">
|
||||
<div v-for="(distributor, index) in articleDistributors" :key="distributor.id || ('new-' + index)" class="wa-distributor-row">
|
||||
<div class="wa-distributor-name">
|
||||
<strong>{{ getDistributorName(distributor.distributorId) }}</strong>
|
||||
<i v-if="distributor.pendingChanges || !distributor.id"
|
||||
class="fas fa-exclamation-triangle text-warning ml-1"
|
||||
style="font-size: 0.75rem;"
|
||||
title="Nicht gespeichert"></i>
|
||||
</div>
|
||||
<div class="wa-distributor-inputs">
|
||||
<tt-input
|
||||
v-model="distributor.externalArticleNumber"
|
||||
@input="distributor.pendingChanges = true"
|
||||
placeholder="Externe Art.-Nr."
|
||||
sm no-form-group/>
|
||||
<tt-input
|
||||
v-model="distributor.purchasePrice"
|
||||
@input="distributor.pendingChanges = true"
|
||||
placeholder="Einkaufspreis €"
|
||||
sm no-form-group/>
|
||||
</div>
|
||||
<div class="wa-distributor-actions">
|
||||
<button class="btn btn-sm btn-primary" @click="saveDistributor(distributor)" title="Speichern">
|
||||
<i class="fas fa-save"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" @click="deleteDistributor(distributor.id)" :disabled="!distributor.id" title="Löschen">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
data: () => ({window, articleDistributors: [], newDistributorId: null}),
|
||||
data: () => ({
|
||||
window,
|
||||
articleDistributors: [],
|
||||
allDistributors: [],
|
||||
showDirectoryModal: false
|
||||
}),
|
||||
async mounted() {
|
||||
await this.fetchArticleDistributors();
|
||||
await Promise.all([
|
||||
this.fetchArticleDistributors(),
|
||||
this.fetchAllDistributors()
|
||||
]);
|
||||
},
|
||||
methods: {
|
||||
async fetchAllDistributors() {
|
||||
const res = await axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseDistributor/get`, {
|
||||
pagination: false,
|
||||
order: { key: 'name', order: 'ASC' }
|
||||
});
|
||||
this.allDistributors = res.data.rows || [];
|
||||
},
|
||||
async fetchArticleDistributors() {
|
||||
const res = await axios.post(`${window['TT_CONFIG']['BASE_PATH']}/WarehouseArticleDistributor/get`, {filters: {articleId: this.id}});
|
||||
this.articleDistributors = res.data.rows;
|
||||
},
|
||||
getDistributorName(id) {
|
||||
const dist = this.allDistributors.find(d => d.id === id);
|
||||
return dist ? dist.name : 'Unbekannt';
|
||||
},
|
||||
addDistributor(distributorId) {
|
||||
if (this.articleDistributors.some(d => d.distributorId === distributorId)) return;
|
||||
this.articleDistributors.push({
|
||||
articleId: this.id,
|
||||
distributorId: distributorId,
|
||||
externalArticleNumber: null,
|
||||
purchasePrice: null,
|
||||
pendingChanges: true
|
||||
});
|
||||
},
|
||||
async saveDistributor(distributor) {
|
||||
delete distributor.pendingChanges;
|
||||
distributor.purchasePrice = distributor.purchasePrice ? parseFloat(distributor.purchasePrice.toString().replace(',', '.')) : null;
|
||||
@@ -130,34 +299,330 @@ Vue.component('warehouse-article-distributor', {
|
||||
await this.fetchArticleDistributors();
|
||||
},
|
||||
async deleteDistributor(distributorId) {
|
||||
if (!confirm('Lieferant wirklich entfernen?')) return;
|
||||
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-modal', {
|
||||
props: {
|
||||
id: { type: [Number, String], required: true }
|
||||
},
|
||||
template: `
|
||||
<div class="modal fade show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5);" @click.self="close">
|
||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-box mr-2"></i>
|
||||
{{ isEditMode ? 'Artikel bearbeiten' : 'Artikel erstellen' }}
|
||||
</h5>
|
||||
<button type="button" class="close" @click="close">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div v-if="loading" class="text-center py-5">
|
||||
<i class="fas fa-spinner fa-spin fa-2x text-muted"></i>
|
||||
</div>
|
||||
<div v-else class="wa-modal-content">
|
||||
<!-- Basic Information -->
|
||||
<div class="wa-section">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<tt-input
|
||||
label="Titel"
|
||||
v-model="formData.title"
|
||||
placeholder="Artikel Titel"
|
||||
required
|
||||
sm/>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<tt-textarea
|
||||
label="Beschreibung"
|
||||
v-model="formData.description"
|
||||
required
|
||||
rows="3"/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<tt-select
|
||||
label="Kategorie"
|
||||
v-model="formData.category_id"
|
||||
:options="categoryOptions"
|
||||
required
|
||||
sm/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<tt-input
|
||||
label="Artikel-Nummer"
|
||||
v-model="formData.articleNumber"
|
||||
placeholder="z.B. 1234"
|
||||
required
|
||||
form-label
|
||||
sm/>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<tt-select
|
||||
label="Einheit"
|
||||
v-model="formData.unit"
|
||||
:options="unitOptions"
|
||||
required
|
||||
sm/>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<tt-select
|
||||
label="Erlöskonto"
|
||||
v-model="formData.revenueAccount"
|
||||
:options="revenueAccountOptions"
|
||||
required
|
||||
sm/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prices Section -->
|
||||
<warehouse-article-prices v-if="isEditMode" :id="Number(id)"/>
|
||||
|
||||
<!-- Distributors Section -->
|
||||
<warehouse-article-distributor v-if="isEditMode" :id="Number(id)"/>
|
||||
|
||||
<!-- Additional Attributes -->
|
||||
<div class="wa-section">
|
||||
<h5 class="wa-section-title"><i class="fas fa-cog mr-2"></i>Zusätzliche Artikel Attribute</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<tt-input
|
||||
label="Warnmenge"
|
||||
type="number"
|
||||
v-model.number="formData.warningAmount"
|
||||
placeholder="0"
|
||||
required
|
||||
sm/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<tt-input
|
||||
label="Kritische Menge"
|
||||
type="number"
|
||||
v-model.number="formData.criticalAmount"
|
||||
placeholder="0"
|
||||
required
|
||||
sm/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wa-checkbox-grid">
|
||||
<label class="wa-checkbox-item">
|
||||
<input type="checkbox" v-model="formData.isSerialDocumentation">
|
||||
<span class="wa-checkmark"></span>
|
||||
<span class="wa-checkbox-label">Seriennummern</span>
|
||||
</label>
|
||||
<label class="wa-checkbox-item">
|
||||
<input type="checkbox" v-model="formData.isEndOfLife">
|
||||
<span class="wa-checkmark"></span>
|
||||
<span class="wa-checkbox-label">End of Life</span>
|
||||
</label>
|
||||
<label class="wa-checkbox-item">
|
||||
<input type="checkbox" v-model="formData.isEShop">
|
||||
<span class="wa-checkmark"></span>
|
||||
<span class="wa-checkbox-label">Ist E-Shop</span>
|
||||
</label>
|
||||
<label class="wa-checkbox-item">
|
||||
<input type="checkbox" v-model="formData.isEShopHide">
|
||||
<span class="wa-checkmark"></span>
|
||||
<span class="wa-checkbox-label">E-Shop Versteckt</span>
|
||||
</label>
|
||||
<label class="wa-checkbox-item">
|
||||
<input type="checkbox" v-model="formData.isSbidiShop">
|
||||
<span class="wa-checkmark"></span>
|
||||
<span class="wa-checkbox-label">Ist SBIDI-Shop</span>
|
||||
</label>
|
||||
<label class="wa-checkbox-item">
|
||||
<input type="checkbox" v-model="formData.isSbidiShopHide">
|
||||
<span class="wa-checkmark"></span>
|
||||
<span class="wa-checkbox-label">SBIDI-Shop Versteckt</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="close">Abbrechen</button>
|
||||
<button type="button" class="btn btn-primary" @click="save" :disabled="saving || !isValid">
|
||||
<i v-if="saving" class="fas fa-spinner fa-spin mr-1"></i>
|
||||
<i v-else class="fas fa-save mr-1"></i>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
data: () => ({
|
||||
loading: false,
|
||||
saving: false,
|
||||
formData: {
|
||||
title: '',
|
||||
description: '',
|
||||
category_id: null,
|
||||
articleNumber: '',
|
||||
unit: 'Stk.',
|
||||
revenueAccount: 0,
|
||||
warningAmount: 0,
|
||||
criticalAmount: 0,
|
||||
isSerialDocumentation: false,
|
||||
isEndOfLife: false,
|
||||
isEShop: false,
|
||||
isEShopHide: false,
|
||||
isSbidiShop: false,
|
||||
isSbidiShopHide: false
|
||||
}
|
||||
}),
|
||||
computed: {
|
||||
isEditMode() {
|
||||
return this.id !== 'create';
|
||||
},
|
||||
categoryOptions() {
|
||||
const catCol = window.TT_CONFIG.CRUD_CONFIG.columns.find(c => c.key === 'category_id');
|
||||
return catCol ? [{ value: null, text: '-- Bitte wählen --' }, ...catCol.modal.items] : [];
|
||||
},
|
||||
unitOptions() {
|
||||
return [
|
||||
{ value: 'Stk.', text: 'Stk.' },
|
||||
{ value: 'Pau.', text: 'Pau.' },
|
||||
{ value: 'm.', text: 'm.' },
|
||||
{ value: 'Std.', text: 'Std.' },
|
||||
{ value: 'km', text: 'km' }
|
||||
];
|
||||
},
|
||||
revenueAccountOptions() {
|
||||
return [
|
||||
{ value: 0, text: 'Dienstleistungen' },
|
||||
{ value: 1, text: 'Handelswaren' }
|
||||
];
|
||||
},
|
||||
isValid() {
|
||||
return this.formData.title &&
|
||||
this.formData.description &&
|
||||
this.formData.category_id &&
|
||||
this.formData.articleNumber &&
|
||||
this.formData.unit;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
||||
document.documentElement.style.overflow = 'hidden';
|
||||
document.documentElement.style.paddingRight = scrollbarWidth + 'px';
|
||||
|
||||
if (this.isEditMode) this.loadArticle();
|
||||
else this.resetForm();
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.documentElement.style.overflow = '';
|
||||
document.documentElement.style.paddingRight = '';
|
||||
},
|
||||
methods: {
|
||||
async loadArticle() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseArticle/getById`, {
|
||||
params: { id: Number(this.id) }
|
||||
});
|
||||
const data = res.data;
|
||||
if (data && data.id) {
|
||||
this.formData = {
|
||||
title: data.title || '',
|
||||
description: data.description || '',
|
||||
category_id: data.category_id,
|
||||
articleNumber: data.articleNumber || '',
|
||||
unit: data.unit || 'Stk.',
|
||||
revenueAccount: data.revenueAccount || 0,
|
||||
warningAmount: data.warningAmount || 0,
|
||||
criticalAmount: data.criticalAmount || 0,
|
||||
isSerialDocumentation: !!data.isSerialDocumentation,
|
||||
isEndOfLife: !!data.isEndOfLife,
|
||||
isEShop: !!data.isEShop,
|
||||
isEShopHide: !!data.isEShopHide,
|
||||
isSbidiShop: !!data.isSbidiShop,
|
||||
isSbidiShopHide: !!data.isSbidiShopHide
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
window.notify('error', 'Fehler beim Laden');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
this.formData = {
|
||||
title: '',
|
||||
description: '',
|
||||
category_id: null,
|
||||
articleNumber: '',
|
||||
unit: 'Stk.',
|
||||
revenueAccount: 0,
|
||||
warningAmount: 0,
|
||||
criticalAmount: 0,
|
||||
isSerialDocumentation: false,
|
||||
isEndOfLife: false,
|
||||
isEShop: false,
|
||||
isEShopHide: false,
|
||||
isSbidiShop: false,
|
||||
isSbidiShopHide: false
|
||||
};
|
||||
},
|
||||
async save() {
|
||||
if (!this.isValid) return;
|
||||
this.saving = true;
|
||||
try {
|
||||
const endpoint = this.isEditMode ? 'update' : 'create';
|
||||
const payload = {
|
||||
...this.formData,
|
||||
isSerialDocumentation: this.formData.isSerialDocumentation ? 1 : 0,
|
||||
isEndOfLife: this.formData.isEndOfLife ? 1 : 0,
|
||||
isEShop: this.formData.isEShop ? 1 : 0,
|
||||
isEShopHide: this.formData.isEShopHide ? 1 : 0,
|
||||
isSbidiShop: this.formData.isSbidiShop ? 1 : 0,
|
||||
isSbidiShopHide: this.formData.isSbidiShopHide ? 1 : 0
|
||||
};
|
||||
if (this.isEditMode) payload.id = Number(this.id);
|
||||
|
||||
const res = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WarehouseArticle/${endpoint}`, payload);
|
||||
if (res.data.success) {
|
||||
window.notify('success', res.data.message || 'Gespeichert');
|
||||
if (!this.isEditMode && window.TT_CONFIG.CRUD_CONFIG.reopenOnCreate) this.$emit('reopen', res.data.id);
|
||||
else this.$emit('close');
|
||||
} else {
|
||||
window.notify('error', res.data.message || 'Fehler beim Speichern');
|
||||
}
|
||||
} catch (e) {
|
||||
window.notify('error', 'Fehler beim Speichern');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('warehouse-article', {
|
||||
template: `
|
||||
<tt-card>
|
||||
<tt-table-crud ref="table" @openHistory="historyModalId = $event.id; historyModal = true">
|
||||
<warehouse-article-modal
|
||||
v-if="articleModalId"
|
||||
:id="articleModalId"
|
||||
@close="articleModalId = null; $refs.table.$refs.table.refreshTable()"
|
||||
@reopen="articleModalId = $event"/>
|
||||
|
||||
<warehouse-history-modal :show.sync="historyModal" :id="historyModalId"/>
|
||||
|
||||
<tt-table-crud
|
||||
ref="table"
|
||||
emit-edit
|
||||
@openHistory="historyModalId = $event.id; historyModal = true"
|
||||
@edit="articleModalId = $event.id">
|
||||
<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
|
||||
@@ -165,7 +630,7 @@ Vue.component('warehouse-article', {
|
||||
<span v-else-if="price && price.title === 'Verkauf'">{{ price.price }} €</span>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-slot:description="{ row }">
|
||||
<tt-tooltip
|
||||
allow-wrapping
|
||||
@@ -175,16 +640,15 @@ Vue.component('warehouse-article', {
|
||||
</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}),
|
||||
data: () => ({
|
||||
window,
|
||||
historyModal: false,
|
||||
historyModalId: null,
|
||||
articleModalId: null
|
||||
}),
|
||||
mounted() {
|
||||
const table = this.$refs.table?.$refs?.table;
|
||||
if (!table) return;
|
||||
@@ -197,9 +661,5 @@ Vue.component('warehouse-article', {
|
||||
if (Object.keys(table.filters).length === 0) table.filters = {};
|
||||
table.refreshTable();
|
||||
}
|
||||
|
||||
window.addEventListener('refreshTable', () => {
|
||||
table.refreshTable();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user