Rechnungsdetails
-
@@ -197,6 +205,12 @@ Vue.component('manual-invoice-modal', {
pdfLoading: false,
pdfPreviewUrl: '',
previewDebounceTimer: null,
+ customerBillingInfo: {
+ billing_type: 'invoice',
+ manual_invoice_sepa_limit: null,
+ vatarea: 'domestic',
+ tax_text: ''
+ },
invoiceData: {
id: null, invoice_number: null, invoice_date: moment().format('YYYY-MM-DD'),
billingaddress_id: null, owner_id: null, customer_number: 0, fibu_account_number: 0,
@@ -205,7 +219,6 @@ Vue.component('manual-invoice-modal', {
leistungszeitraum: '', einleitender_text: '', externe_referenz: '', gesamtrabatt: 0,
positions: [], total: 0, total_gross: 0
},
- billingTypeOptions: [{value: 'invoice', text: 'Rechnung'}, {value: 'sepa', text: 'SEPA'}],
positionsConfig: {
fields: {
article_id: {
@@ -270,16 +283,31 @@ Vue.component('manual-invoice-modal', {
});
return { subtotal, net, vat, gross };
+ },
+ effectiveBillingType() {
+ if (this.customerBillingInfo.billing_type !== 'sepa') return 'invoice';
+ if (this.customerBillingInfo.manual_invoice_sepa_limit === null) return 'sepa';
+ return this.totals.gross <= this.customerBillingInfo.manual_invoice_sepa_limit ? 'sepa' : 'invoice';
}
},
watch: {
'invoiceData': { handler() { this.debouncedPreviewUpdate(); }, deep: true },
+ effectiveBillingType: {
+ handler(newType) {
+ this.invoiceData.billing_type = newType;
+ },
+ immediate: true
+ },
'invoiceData.billingaddress_id': {
async handler(newId) {
- if (!newId) return Object.assign(this.invoiceData, {
- company: '', firstname: '', lastname: '', street: '', zip: '', city: '',
- country: 'Österreich', uid: '', email: '', customer_number: 0, fibu_account_number: 0, owner_id: 0
- });
+ if (!newId) {
+ Object.assign(this.invoiceData, {
+ company: '', firstname: '', lastname: '', street: '', zip: '', city: '',
+ country: 'Österreich', uid: '', email: '', customer_number: 0, fibu_account_number: 0, owner_id: 0
+ });
+ this.customerBillingInfo = { billing_type: 'invoice', manual_invoice_sepa_limit: null, vatarea: 'domestic', tax_text: '' };
+ return;
+ }
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/Address/api?do=getAddress&id=${newId}`);
if (data.status === 'OK' && data.result.address) {
@@ -291,6 +319,8 @@ Vue.component('manual-invoice-modal', {
fibu_account_number: a.fibu_account_number || 0, owner_id: newId
});
}
+
+ await this.fetchCustomerBillingInfo(newId);
}
}
},
@@ -327,10 +357,35 @@ Vue.component('manual-invoice-modal', {
methods: {
close() { this.$emit('close'); },
saveInvoice() {
+ this.invoiceData.invoice_date = moment().format('YYYY-MM-DD');
+ this.invoiceData.billing_type = this.effectiveBillingType;
+ this.invoiceData.tax_text = this.customerBillingInfo.tax_text;
if (!this.invoiceData.billingaddress_id) return window.notify('error', 'Bitte wählen Sie einen Kunden aus.');
if (!this.invoiceData.positions?.length) return window.notify('error', 'Bitte fügen Sie mindestens eine Position hinzu.');
this.$emit('save', this.invoiceData);
},
+ formatPrice(value) {
+ if (value === null || value === undefined) return '-';
+ return new Intl.NumberFormat('de-AT', { style: 'currency', currency: 'EUR' }).format(value);
+ },
+ async fetchCustomerBillingInfo(addressId) {
+ if (!addressId) return;
+ try {
+ const vatgroupId = this.invoiceData.vatgroup_id || 2;
+ const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getCustomerBillingInfo?address_id=${addressId}&vatgroup_id=${vatgroupId}`);
+ if (data.success) {
+ this.customerBillingInfo = {
+ billing_type: data.billing_type || 'invoice',
+ manual_invoice_sepa_limit: data.manual_invoice_sepa_limit,
+ vatarea: data.vatarea || 'domestic',
+ tax_text: data.tax_text || ''
+ };
+ this.invoiceData.tax_text = data.tax_text || '';
+ }
+ } catch (e) {
+ console.error('Error fetching customer billing info:', e);
+ }
+ },
handleResize() { this.isLargeScreen = window.innerWidth >= 1920; },
handleGlobalKeydown(e) {
if (e.ctrlKey && e.key === 'q') { e.preventDefault(); this.togglePreviewVisibility(); }
@@ -339,9 +394,9 @@ Vue.component('manual-invoice-modal', {
async onArticleSelected(articleId) {
if (!articleId) return;
try {
- const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getArticleVatInfo?article_id=${articleId}`);
+ const vatarea = this.customerBillingInfo.vatarea || 'domestic';
+ const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getArticleVatInfo?article_id=${articleId}&vatarea=${vatarea}`);
if (data.success && this.$refs.positionsManager) {
- // Update the formData in the positions manager
const pm = this.$refs.positionsManager;
if (data.article) {
pm.$set(pm.formData, 'product_name', data.article.title);
@@ -351,13 +406,26 @@ Vue.component('manual-invoice-modal', {
pm.$set(pm.formData, 'fibu_cost_account', data.fibu_cost_account);
pm.$set(pm.formData, 'fibu_cost_account_legacy', data.fibu_cost_account_legacy);
pm.$set(pm.formData, 'fibu_taxcode', data.fibu_taxcode);
- // Store vatgroup_id on invoice level if needed
this.invoiceData.vatgroup_id = data.vatgroup_id;
+ await this.updateTaxText(data.vatgroup_id);
}
} catch (e) {
console.error('Error fetching article VAT info:', e);
}
},
+ async updateTaxText(vatgroupId) {
+ if (!vatgroupId) return;
+ try {
+ const vatarea = this.customerBillingInfo.vatarea || 'domestic';
+ const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}/ManualInvoice/getTaxText?vatgroup_id=${vatgroupId}&vatarea=${vatarea}`);
+ if (data.success) {
+ this.customerBillingInfo.tax_text = data.tax_text || '';
+ this.invoiceData.tax_text = data.tax_text || '';
+ }
+ } catch (e) {
+ console.error('Error fetching tax text:', e);
+ }
+ },
debouncedPreviewUpdate() {
clearTimeout(this.previewDebounceTimer);
this.previewDebounceTimer = setTimeout(() => this.updatePdfPreview(), 2000);
diff --git a/public/js/pages/WorkorderBase/WorkorderBase.js b/public/js/pages/WorkorderBase/WorkorderBase.js
index 3972717c4..0b7d2c244 100644
--- a/public/js/pages/WorkorderBase/WorkorderBase.js
+++ b/public/js/pages/WorkorderBase/WorkorderBase.js
@@ -273,7 +273,44 @@ Vue.component('workorder-details-manager', {
/>
-
+
+
+
+
+
+
+
Patchposition
+
+
+ | Equipment Name: |
+ {{ technicalData.patchposition.equipmentName }} |
+
+
+ | Equipment Port: |
+ {{ technicalData.patchposition.equipmentPort }} |
+
+
+
+
+
AHA Blätter
+
+
+
+ {{ wo.rimoName }}
+ {{ wo.rimoStatus }}
+
+
+ AHA Blatt
+
+
+
+
+
+
+
+
@@ -328,6 +365,9 @@ Vue.component('workorder-details-manager', {
requireCableLength: false,
requireCableType: false,
savingData: false,
+ // Technical data
+ showTechnicalData: false,
+ technicalData: null,
// Admin state
selectedDocs: [], correctionText: '', correctionLoading: false, showAcceptModal: false, showRevertModal: false,
}),
@@ -394,6 +434,8 @@ Vue.component('workorder-details-manager', {
this.interventionTypes = data.interventionTypes;
this.requireCableLength = data.requireCableLength || false;
this.requireCableType = data.requireCableType || false;
+ this.showTechnicalData = data.showTechnicalData || false;
+ this.technicalData = data.technicalData || null;
}
} catch (e) { console.error("Mandantenkonfiguration nicht geladen", e); }
finally { this.loadingConfig = false; }
diff --git a/public/js/pages/WorkorderMphAdmin/WorkorderMphAdmin.js b/public/js/pages/WorkorderMphAdmin/WorkorderMphAdmin.js
index 72191b6e8..be5226746 100644
--- a/public/js/pages/WorkorderMphAdmin/WorkorderMphAdmin.js
+++ b/public/js/pages/WorkorderMphAdmin/WorkorderMphAdmin.js
@@ -30,10 +30,13 @@ Vue.component('workorder-mph-admin', {
{{ row.companyName || 'N/A' }}
-
-
+
+
@@ -101,6 +104,13 @@ Vue.component('workorder-mph-admin', {
Soll der Auftrag #{{ cancelWorkorderModalData.id }} wirklich storniert werden?
+
+
+ Soll die Zuweisung für Auftrag #{{ unassignWorkorderModalData.id }} aufgehoben werden?
+ Aktuell zugewiesen an: {{ unassignWorkorderModalData.companyName }}
+
+
`,
data() {
@@ -113,6 +123,7 @@ Vue.component('workorder-mph-admin', {
companies: [],
companiesLoading: false,
cancelWorkorderModalData: null,
+ unassignWorkorderModalData: null,
crudConfig: {
...window.TT_CONFIG.CRUD_CONFIG,
selectable: false,
@@ -237,6 +248,20 @@ Vue.component('workorder-mph-admin', {
} else {
window.notify('error', data.message || 'Stornierung fehlgeschlagen.');
}
+ },
+ async unassignWorkorder() {
+ const { id, reason } = this.unassignWorkorderModalData;
+ const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphAdmin/unassignWorkorder`, {
+ workorderId: id,
+ reason: reason
+ });
+ if (data.success) {
+ window.notify('success', data.message);
+ this.$refs.table.$refs.table.refreshTable();
+ this.unassignWorkorderModalData = null;
+ } else {
+ window.notify('error', data.message || 'Aufheben der Zuweisung fehlgeschlagen.');
+ }
}
}
});
\ No newline at end of file
diff --git a/public/js/pages/WorkorderTenantConfig/WorkorderTenantConfig.js b/public/js/pages/WorkorderTenantConfig/WorkorderTenantConfig.js
index 681f3bca6..d2a1b36a4 100644
--- a/public/js/pages/WorkorderTenantConfig/WorkorderTenantConfig.js
+++ b/public/js/pages/WorkorderTenantConfig/WorkorderTenantConfig.js
@@ -89,6 +89,8 @@ Vue.component('workorder-tenant-config', {
v-model="editableItem.requireCableLength" sm/>
+
Workorder: {{ config.enableWorkorder ? 'Aktiviert' : 'Deaktiviert' }}
@@ -97,6 +99,7 @@ Vue.component('workorder-tenant-config', {
Tiefbau-Doku: {{ config.civilEngineeringDocsRequired ? 'Ja' : 'Nein' }}
Kabellänge-Doku: {{ config.requireCableLength ? 'Ja' : 'Nein' }}
Kabeltyp-Doku: {{ config.requireCableType ? 'Ja' : 'Nein' }}
+
Technische Daten: {{ config.showTechnicalData ? 'Ja' : 'Nein' }}
@@ -333,6 +336,7 @@ Vue.component('workorder-tenant-config', {
civilEngineeringDocsRequired: 0,
requireCableLength: 0,
requireCableType: 0,
+ showTechnicalData: 0,
enableWorkorder: 1,
enableWorkorderMph: 1
}
diff --git a/public/mobile/modules/workorder/WorkorderModule.js b/public/mobile/modules/workorder/WorkorderModule.js
index 1324c1da9..c760aca46 100644
--- a/public/mobile/modules/workorder/WorkorderModule.js
+++ b/public/mobile/modules/workorder/WorkorderModule.js
@@ -31,6 +31,7 @@ export default {
const documentation = ref({ docs: [], journals: [] });
const tenantConfig = ref(null);
const checklist = ref([]);
+ const technicalData = ref(null);
// Expanded cards state
const expandedCards = ref({
@@ -39,7 +40,8 @@ export default {
documentation: false,
notes: false,
journal: false,
- cableData: false
+ cableData: false,
+ technical: true
});
// Edit states
@@ -52,6 +54,9 @@ export default {
const showDocUploadSheet = ref(false);
const showProblemSheet = ref(false);
const showCompleteSheet = ref(false);
+ const showPdfViewer = ref(false);
+ const pdfViewerUrl = ref('');
+ const pdfViewerTitle = ref('');
// Upload state
const uploadDocType = ref('');
@@ -198,7 +203,7 @@ export default {
const openDetail = async (workorder) => {
selectedWorkorder.value = workorder;
isDetailLoading.value = true;
- expandedCards.value = { customer: true, checklist: true, documentation: false, notes: false, journal: false, cableData: false };
+ expandedCards.value = { customer: true, checklist: true, documentation: false, notes: false, journal: false, cableData: false, technical: true };
emit('detail-open', workorder.id);
try {
@@ -215,6 +220,7 @@ export default {
documentation.value = { docs: data.docs, journals: data.journals };
tenantConfig.value = data.tenantConfig;
checklist.value = data.checklist;
+ technicalData.value = data.technicalData || null;
} else {
emit('toast', data.message || 'Fehler beim Laden', 'error');
}
@@ -231,6 +237,7 @@ export default {
documentation.value = { docs: [], journals: [] };
tenantConfig.value = null;
checklist.value = [];
+ technicalData.value = null;
isEditingNotes.value = false;
emit('detail-close');
};
@@ -617,6 +624,13 @@ export default {
// Button is disabled when not complete, so this won't be called
};
+ // Open PDF in viewer
+ const openPdfViewer = (url, title) => {
+ pdfViewerUrl.value = url;
+ pdfViewerTitle.value = title || 'PDF';
+ showPdfViewer.value = true;
+ };
+
// Initialize
onMounted(() => {
fetchWorkorders();
@@ -636,6 +650,7 @@ export default {
documentation,
tenantConfig,
checklist,
+ technicalData,
expandedCards,
isEditingNotes,
tempNotes,
@@ -644,6 +659,9 @@ export default {
showDocUploadSheet,
showProblemSheet,
showCompleteSheet,
+ showPdfViewer,
+ pdfViewerUrl,
+ pdfViewerTitle,
uploadDocType,
isUploading,
fileInputRef,
@@ -678,6 +696,7 @@ export default {
openNavigation,
callCustomer,
handleComplete,
+ openPdfViewer,
handleTouchStart,
handleTouchMove,
handleTouchEnd,
@@ -906,6 +925,63 @@ export default {
+
+
+
+
+
+
+
Patchposition
+
+
+ Equipment Name:
+ {{ technicalData.patchposition.equipmentName }}
+
+
+ Equipment Port:
+ {{ technicalData.patchposition.equipmentPort }}
+
+
+
+
+
+
+
AHA Blätter
+
+
+
{{ wo.rimoName }}
+
Status: {{ wo.rimoStatus }}
+
+
+
+
+
+
+