Merge branch 'feature/warehouse-shipping-note-additional-fix' into 'master'

switched 2 inputs

See merge request fronk/thetool!754
This commit is contained in:
Luca Haid
2024-11-20 12:26:23 +00:00
5 changed files with 118 additions and 46 deletions

View File

@@ -15,7 +15,7 @@ class WarehouseShippingNoteController extends TTCrud {
['key' => 'deliveryAddressLine', 'text' => 'L.-Adr.', 'required' => true],
['key' => 'deliveryAddressPLZ', 'text' => 'L.-Adr. PLZ', 'required' => true],
['key' => 'deliveryAddressEMail', 'text' => 'L.-Adr. EMail', 'required' => true, 'table' => false],
['key' => 'note', 'text' => 'Notiz', 'required' => true, 'table' => false],
['key' => 'note', 'text' => 'Art der Arbeit', 'required' => true, 'table' => false],
['key' => 'status',
'text' => 'Status',
'required' => true,
@@ -387,6 +387,37 @@ class WarehouseShippingNoteController extends TTCrud {
die(json_encode(['success' => true, 'status' => 'USER_NO_CAR']));
}
protected function geoAutocompleteAction() {
$search = $this->request->q;
$search = urlencode($search);
$url = "https://nominatim.haid.in/search?q=$search&format=json";
$data = json_decode(file_get_contents($url), true);
$out = [];
foreach ($data as $entry) {
$parsedDisplayNameParts = [];
foreach(explode(',', $entry['display_name']) as $part) {
// if str_includes Bezirk remove it
if (strpos($part, 'Bezirk') !== false) {
continue;
}
$parsedDisplayNameParts[] = $part;
}
$out[] = ['value' => $entry['lat'] . "," . $entry['lon'], 'text' => implode(',', $parsedDisplayNameParts)];
}
self::returnJson($out);
}
protected function geoReverseAction() {
$lat = $this->request->lat;
$lon = $this->request->lon;
$url = "https://nominatim.haid.in/reverse?lat=$lat&lon=$lon&format=json";
$data = json_decode(file_get_contents($url), true);
self::returnJson($data);
}
//TODO: export this to an api class for openstreetmap
protected function getDistanceAction() {
@@ -413,7 +444,7 @@ class WarehouseShippingNoteController extends TTCrud {
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://nominatim.openstreetmap.org/search?q=$address&format=json",
CURLOPT_URL => "https://nominatim.haid.in/search?q=$address&format=json",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_ENCODING => "",

View File

@@ -36,6 +36,10 @@
grid-gap: 10px;
}
.warehouse-shipping-note-modal-positions-entry-actions, .warehouse-shipping-note-modal-hours-entry-actions {
grid-column: 2;
}
.signModal > div {
margin: 0;
width: 100vw;

View File

@@ -96,9 +96,11 @@ Vue.component('warehouse-shipping-note-modal-hours-entry', {
this.kilometerCount = response.data.distance
},
async updateCarId() {
if (!this.userId || this.carId) return;
const response = await axios.get(window.TT_CONFIG["BASE_PATH"] + '/WarehouseShippingNote/timerecordingCarForUser?userId=' + this.userId);
if (response.data.status === 'USER_NO_CAR') {
this.window.notify('info', 'Kein zugewiesenes Fahrzeug gefunden');
this.carId = '';
return;
}
this.carId = response.data.id;
@@ -114,8 +116,8 @@ Vue.component('warehouse-shipping-note-modal-hours-entry', {
}
},
async mounted() {
if (!this.carId) this.updateCarId().then();
if (!this.userId) this.userId = this.window.TT_CONFIG['USER_ID'];
if (!this.carId) this.updateCarId().then();
if (!this.date) this.updateDate();
if (!this.kilometerCount) this.updateKilometerCount().then();
@@ -273,7 +275,7 @@ Vue.component('warehouse-shipping-note-modal-positions-entry', {
if (!this.price) return this.window.notify('error', 'Bitte füllen sie den Preis aus');
const data = {
amount: this.amount,
price: parseFloat(this.price)
price: parseFloat(this.price)
}
if (!this.articleId && this.$refs.article.displayValue) {
data.articleText = this.$refs.article.displayValue;
@@ -331,7 +333,9 @@ Vue.component('warehouse-shipping-note-modal-positions-view', {
<td colspan="4" class="text-center">Keine Einträge</td>
</tr>
<tr v-for="position in positions">
<td>{{ position.article ? articleNames[position.article] : position.articlePacket ? articlePacketNames[position.articlePacket] : position.articleText }}</td>
<td>{{ position.article ? articleNames[position.article] : position.articlePacket ? articlePacketNames[position.articlePacket] :
position.articleText }}
</td>
<td>{{ position.amount }}</td>
<td>{{ (position.price?.toFixed(2)) }} €</td>
<td>
@@ -415,7 +419,7 @@ Vue.component('warehouse-shipping-note-modal-positions', {
},
editEntry(entry) {
this.selectedUpdateIndex = this.positions.indexOf(entry);
if (entry.article)this.$refs.entry.articleId = entry.article;
if (entry.article) this.$refs.entry.articleId = entry.article;
if (entry.articlePacket) this.$refs.entry.articlePacketId = entry.articlePacket;
if (entry.articleText) this.$refs.entry.$refs.article.displayValue = entry.articleText;
this.$refs.entry.amount = entry.amount;
@@ -460,14 +464,14 @@ Vue.component('warehouse-shipping-note-modal', {
:del-addr-e-mail.sync="delAddrEMail"/>
<template v-if="billAddrId && delAddrName && delAddrLine && delAddrPLZ && delAddrCity">
<div v-show="delAddrFilled === false">
<hr>
<h4 class="text-center">Textelemente</h4>
<warehouse-shipping-note-modal-text-elements :text-elements="textElements"/>
<hr>
<tt-textarea label="Einleitender Text" v-model="note" sm row/>
<tt-textarea label="Art der Arbeit" v-model="note" sm row/>
<hr>
@@ -478,16 +482,16 @@ Vue.component('warehouse-shipping-note-modal', {
<hr>
<h4 class="text-center">Positionen</h4>
<warehouse-shipping-note-modal-positions :positions.sync="positions" :bill-addr-id="billAddrId"/>
</template>
</div>
<div v-show="delAddrFilled === true" class="text-center">Bitte füllen Sie die Rechnungs- und Lieferadresse aus</div>
<div v-else class="text-center">Bitte füllen Sie die Rechnungs- und Lieferadresse aus</div>
</div>
<!-- TODO: fix these buttons-->
<template v-slot:footer-prepend v-if="id !== 'create'">
<button class="btn btn-info" @click="$emit('open-signing-modal', id)">Unterschreiben</button>
<!-- <button class="btn btn-success" @click="alert('Accept')">Akzeptieren</button>-->
<!-- <button class="btn btn-warning" @click="alert('Invoiced')">Verrechnet</button>-->
<!-- <button class="btn btn-success" @click="alert('Accept')">Akzeptieren</button>-->
<!-- <button class="btn btn-warning" @click="alert('Invoiced')">Verrechnet</button>-->
</template>
</tt-modal>
`,
@@ -553,13 +557,15 @@ Vue.component('warehouse-shipping-note-modal', {
computed: {
title() {
return this.id === 'create' ? 'Lieferschein erstellen' : `Lieferschein #${this.id} bearbeiten`;
},
delAddrFilled() {
return !this.delAddrName || !this.delAddrLine || !this.delAddrPLZ || !this.delAddrCity;
}
}
})
Vue.component('warehouse-shipping-note-modal-address', {
// also add props for delAddrName, delAddrLine, delAddrPLZ, delAddrCity which we will sync with the parent component
props: {
billAddrId: {type: [String, Number], required: true},
delAddrName: {type: String, required: true},
@@ -578,6 +584,7 @@ Vue.component('warehouse-shipping-note-modal-address', {
addresses: [],
fetchedBillAddr: null,
selectedAddr: '',
newAddrGeoLatLon: '',
}
},
//language=Vue
@@ -590,11 +597,14 @@ Vue.component('warehouse-shipping-note-modal-address', {
</template>
<template v-else-if="addressMode === 'new'">
<tt-input v-model="delAddrName" label="Lieferadresse Name" sm row/>
<tt-input v-model="delAddrLine" label="Lieferadresse" sm row/>
<tt-input v-model="delAddrPLZ" label="Lieferadresse PLZ" sm row/>
<tt-input v-model="delAddrCity" label="Lieferadresse Ort" sm row/>
<tt-input v-model="delAddrEMail" label="Lieferadresse E-Mail" sm row/>
<tt-input :value="delAddrName" @input="$emit('update:delAddrName', $event)" label="Lieferadresse Name*" sm row/>
<tt-input :value="delAddrEMail" @input="$emit('update:delAddrEMail', $event)" label="Lieferadresse E-Mail" sm row/>
<tt-autocomplete :api-url="window.TT_CONFIG['BASE_PATH'] + '/WarehouseShippingNote/geoAutocomplete'" @input="newAddrGeoLatLon = $event" label="Adresse*" sm row/>
<span v-if="delAddrLine && delAddrPLZ && delAddrCity">Adresse: {{ delAddrLine }}, {{ delAddrPLZ }} {{ delAddrCity }}</span>
<!-- <tt-input :value="delAddrLine" @input="$emit('update:delAddrLine', $event)" label="Lieferadresse" sm row/>-->
<!-- <tt-input :value="delAddrPLZ" @input="$emit('update:delAddrPLZ', $event)" label="Lieferadresse PLZ" sm row/>-->
<!-- <tt-input :value="delAddrCity" @input="$emit('update:delAddrCity', $event)" label="Lieferadresse Ort" sm row/>-->
</template>
</div>
`,
@@ -602,24 +612,34 @@ Vue.component('warehouse-shipping-note-modal-address', {
billAddrId: {handler: 'updateBillingMode', immediate: false},
addressMode: {handler: 'fetchDeliveryAddresses', immediate: false},
selectedAddr: {handler: 'setSelectedAddrValues', immediate: false},
newAddrGeoLatLon: {handler: 'fetchGeoAddress', immediate: false},
},
methods: {
async fetchGeoAddress() {
if (!this.newAddrGeoLatLon) return;
const [lat, lon] = this.newAddrGeoLatLon.split(',');
const response = await axios.get(window.TT_CONFIG["BASE_PATH"] + '/WarehouseShippingNote/geoReverse?lat=' + lat + '&lon=' + lon);
if (response.data.address.road) {
this.$emit('update:delAddrLine', `${response.data.address.road}${response.data.address.house_number ? ' ' + response.data.address.house_number : ''}`);
} else {
this.$emit('update:delAddrLine', `${response.data.address.village}${response.data.address.house_number ? ' ' + response.data.address.house_number : ''}`);
}
this.$emit('update:delAddrPLZ', response.data.address.postcode);
this.$emit('update:delAddrCity', response.data.address.village || response.data.address.city || response.data.address.town);
},
async updateBillingMode() {
await this.fetchDeliveryAddresses();
// this.addressMode = 'billing';
console.log('updateBillingMode');
// Here we check if the address is already in the list of addresses, if not we will set the addressMode to billing and fetch the billing address
if (this.delAddrName && this.delAddrLine && this.delAddrPLZ && this.delAddrCity) {
const foundAddress = this.addresses.find(address => address.deliveryAddressName ===
this.delAddrName &&
address.deliveryAddressLine ===
this.delAddrLine &&
address.deliveryAddressPLZ ===
this.delAddrPLZ &&
address.deliveryAddressCity ===
this.delAddrCity && address.deliveryAddressEMail === this.delAddrEMail);
const foundAddress = this.addresses.find(address =>
address.deliveryAddressName === this.delAddrName &&
address.deliveryAddressLine === this.delAddrLine &&
address.deliveryAddressPLZ === this.delAddrPLZ &&
address.deliveryAddressCity === this.delAddrCity &&
address.deliveryAddressEMail === this.delAddrEMail);
if (foundAddress) {
this.addressMode = 'existing';
this.selectedAddr = foundAddress.id;
@@ -631,11 +651,21 @@ Vue.component('warehouse-shipping-note-modal-address', {
await this.fetchBillingAddress();
}
},
async fetchDeliveryAddresses() {
async fetchDeliveryAddresses(newVal, oldVal) {
if ((oldVal === 'billing' || oldVal === 'existing') && newVal === 'new') {
this.$emit('update:delAddrName', '');
this.$emit('update:delAddrLine', '');
this.$emit('update:delAddrPLZ', '');
this.$emit('update:delAddrCity', '');
this.$emit('update:delAddrEMail', '');
return;
}
if (this.addressMode === 'billing' && this.billAddrId) {
await this.fetchBillingAddress();
return;
}
if (!this.billAddrId || this.addressMode !== 'existing' || this.fetchedBillAddr === this.billAddrId) return;
const response = await axios.get(window.TT_CONFIG["BASE_PATH"] + '/WarehouseShippingNote/getDeliveryAddresses?billingAddressId=' + this.billAddrId);
@@ -667,7 +697,8 @@ Vue.component('warehouse-shipping-note-modal-address', {
this.window.notify('error', 'Rechnungsadresse konnte nicht gefunden werden');
return;
}
this.window.notify('success', 'Rechnungsadresse gefunden');
// TODO: here is still a bug that we fetch the billing address twice
// this.window.notify('success', 'Rechnungsadresse gefunden');
this.$emit('update:delAddrName',
response.data.result.address.company || response.data.result.address.firstname + ' ' + response.data.result.address.lastname);
@@ -690,9 +721,9 @@ Vue.component('warehouse-shipping-note-signature-pad', {
},
data() {
return {
window: window,
signaturePad: null,
shippingNote: null,
window: window,
signaturePad: null,
shippingNote: null,
signatureName: '',
}
},
@@ -700,19 +731,24 @@ Vue.component('warehouse-shipping-note-signature-pad', {
template: `
<tt-modal class="signModal" :show="true" :delete="false" :submit="false" @update:show="$emit('close')" :title="'Unterschrift'">
<div style="max-width: 520px;display: flex; flex-direction: column; align-items: center;">
<div style="width: 480px"><tt-input v-model="signatureName" label="Name" row/></div>
<div><canvas id="signature-pad" width="500" height="200" style="border: 1px solid black"></canvas></div>
<div>
<button class="btn btn-primary" @click="submit()">Speichern</button>
<button class="btn btn-primary" @click="signaturePad.clear()">Leeren</button>
</div>
<div style="width: 480px">
<tt-input v-model="signatureName" label="Name" row/>
</div>
<div>
<canvas id="signature-pad" width="500" height="200" style="border: 1px solid black"></canvas>
</div>
<div>
<button class="btn btn-primary" @click="submit()">Speichern</button>
<button class="btn btn-primary" @click="signaturePad.clear()">Leeren</button>
</div>
</div>
</tt-modal>
`,
methods: {
async submit() {
const data = this.signaturePad.toDataURL();
const response = await axios.post(window.TT_CONFIG['BASE_PATH'] + '/WarehouseShippingNote/sign?id=' + this.shippingNoteId, {signature: data, signatureName: this.signatureName});
const response = await axios.post(window.TT_CONFIG['BASE_PATH'] + '/WarehouseShippingNote/sign?id=' + this.shippingNoteId,
{signature: data, signatureName: this.signatureName});
if (response.data.success) {
this.window.notify('success', response.data.message || 'Erfolgreich unterschrieben');
this.$emit('close');

View File

@@ -99,7 +99,7 @@ Vue.component('tt-autocomplete', {
if (this.value && this.apiUrl) {
const response = await axios.get(`${this.apiUrl}&autocomplete=1&searchedID=${this.value}`);
const response = await axios.get(`${this.apiUrl}${this.apiUrl.includes('?') ? '&' : '?'}autocomplete=1&searchedID=${this.value}`);
this.displayValue = response.data[0].text;
} else if (this.value) {
const selectedItem = this.items.find(item => item.value === this.value);
@@ -145,7 +145,6 @@ Vue.component('tt-autocomplete', {
this.isLoading = true;
clearTimeout(this.fetchSuggestionsDebounceTimer);
console.log(this.displayValue);
this.fetchSuggestionsDebounceTimer = setTimeout(() => {
setTimeout(async () => {
@@ -155,7 +154,7 @@ Vue.component('tt-autocomplete', {
return;
}
const response = await axios.get(`${this.apiUrl}&autocomplete=1&q=${encodeURIComponent(this.displayValue)}`);
const response = await axios.get(`${this.apiUrl}${this.apiUrl.includes('?') ? '&' : '?'}autocomplete=1&q=${encodeURIComponent(this.displayValue)}`);
if (response.data?.status === 'error') {
this.displayingItems = [];
} else {

View File

@@ -38,7 +38,9 @@ Vue.component('tt-modal', {
this.$emit('update:show', false)
}
if (event.key === 'Enter' && this.save) {
// only submit
if (event.target.tagName === 'TEXTAREA' || event.target.tagName === 'INPUT') {
return
}
this.$emit('submit')
}
}