Merge branch 'Warehouse/fix-and-improve' into 'master'
Added WarehouseOffer and WarehouseOfferTemplate, also fixed menu for Lager Point See merge request fronk/thetool!1156
This commit is contained in:
@@ -169,9 +169,10 @@
|
||||
<ul class="submenu">
|
||||
<?php if($me->can("WarehouseAdmin") || $me->can("WarehouseUser")): ?><li class="has-sub-submenu font-weight-bold"><a>XINON</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin") || $me->can("WarehouseUser")): ?><li><a href="<?=self::getUrl("WarehouseArticle")?>"><i class="far fa-fw fa-box text-info"></i> Artikel</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseItem")?>"><i class="far fa-fw fa-boxes text-info"></i> Lagerbestand (WIP)</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseOrderRecommendation")?>"><i class="far fa-fw fa-box-full text-info"></i> Bestellvorschläge (WIP)</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseOrder")?>"><i class="far fa-fw fa-shopping-bag text-info"></i> Bestellungen (WIP)</a></li><?php endif; ?>
|
||||
<!-- --><?php //if($me->can("WarehouseAdmin")): ?><!--<li><a href="--><?php //=self::getUrl("WarehouseItem")?><!--"><i class="far fa-fw fa-boxes text-info"></i> Lagerbestand (WIP)</a></li>--><?php //endif; ?>
|
||||
<!-- --><?php //if($me->can("WarehouseAdmin")): ?><!--<li><a href="--><?php //=self::getUrl("WarehouseOrderRecommendation")?><!--"><i class="far fa-fw fa-box-full text-info"></i> Bestellvorschläge (WIP)</a></li>--><?php //endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseOrder")?>"><i class="far fa-fw fa-shopping-bag text-info"></i> Bestellungen</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin")): ?><li><a href="<?=self::getUrl("WarehouseOffer")?>"><i class="far fa-fw fa-file-signature text-info"></i> Angebote</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin") || $me->can("WarehouseUser")): ?><li><a href="<?=self::getUrl("WarehouseOrderRequest")?>"><i class="far fa-fw fa-shopping-cart text-info"></i> Bestellwünsche</a></li><?php endif; ?>
|
||||
<?php if($me->can("WarehouseAdmin") || $me->can("WarehouseUser")): ?><li><a href="<?=self::getUrl("WarehouseShippingNote")?>"><i class="far fa-fw fa-shipping-fast text-info"></i> Lieferscheine</a></li><?php endif; ?>
|
||||
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
class WarehouseOfferController extends TTCrud {
|
||||
protected string $headerTitle = 'Angebote';
|
||||
protected string $singleText = 'Angebot';
|
||||
protected bool $createText = false;
|
||||
|
||||
protected array $columns = [
|
||||
['key' => 'id', 'text' => 'ID', 'modal' => false],
|
||||
['key' => 'id', 'text' => 'ID', 'modal' => false, 'table' => false],
|
||||
['key' => 'offerNumber', 'text' => 'Angebotsnummer', 'required' => true, 'modal' => false],
|
||||
['key' => 'customerNumber', 'text' => 'Kundennummer', 'required' => true, 'modal' => false],
|
||||
['key' => 'customerName', 'text' => 'Kundenname', 'required' => true, 'modal' => false],
|
||||
['key' => 'customerCity', 'text' => 'Stadt', 'required' => true, 'modal' => false],
|
||||
['key' => 'customerVAT', 'text' => 'UID', 'required' => true, 'modal' => false],
|
||||
['key' => 'editor', 'text' => 'Sachbearbeiter', 'required' => true, 'modal' => false],
|
||||
['key' => 'editor', 'text' => 'Sachbearbeiter', 'required' => true, 'modal' => ['type' => 'select'], 'table' => ['filter' => 'select']],
|
||||
['key' => 'totalAmount', 'text' => 'Gesamtbetrag', 'required' => true, 'modal' => false],
|
||||
['key' => 'status', 'text' => 'Status', 'required' => true, 'modal' => ['type' => 'select']],
|
||||
['key' => 'status', 'text' => 'Status', 'required' => true],
|
||||
['key' => 'create', 'text' => 'Erstellt', 'required' => true, 'modal' => false],
|
||||
['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'modal' => ['type' => 'select']],
|
||||
['key' => 'createBy', 'text' => 'Erstellt von', 'required' => true, 'modal' => ['type' => 'select'], 'table' => ['filter' => 'select']],
|
||||
['key' => 'actions',
|
||||
'text' => 'Aktionen',
|
||||
'required' => false,
|
||||
@@ -24,27 +25,19 @@ class WarehouseOfferController extends TTCrud {
|
||||
];
|
||||
|
||||
protected array $permissionCheck = ['WarehouseAdmin'];
|
||||
protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']];
|
||||
|
||||
protected array $additionalActions = [
|
||||
['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary'],
|
||||
['key' => 'sendOffer', 'title' => 'Angebot senden', 'class' => 'fas fa-paper-plane text-success']
|
||||
];
|
||||
|
||||
protected array $additionalJS = ['
|
||||
https://cdn.jsdelivr.net/npm/sortablejs@1.14.0/Sortable.min.js
|
||||
https://cdn.jsdelivr.net/npm/vue-draggable-next@2.1.0'];
|
||||
|
||||
protected array $infoMessages = [
|
||||
'create' => 'Angebot wurde erfolgreich erstellt.',
|
||||
'update' => 'Angebot wurde aktualisiert.',
|
||||
'delete' => 'Angebot wurde gelöscht',
|
||||
'noChanges' => 'Keine Änderungen',
|
||||
'sent' => 'Angebot wurde erfolgreich gesendet',
|
||||
];
|
||||
protected function prepareCrudConfig(): void {
|
||||
$editorColumnIndex = array_search('editor', array_column($this->columns, 'key'));
|
||||
$this->columns[$editorColumnIndex]['modal']['items'] = array_map(function ($user) {
|
||||
return ['value' => intval($user->id), 'text' => $user->name];
|
||||
}, UserModel::search(['employee' => true]));
|
||||
}
|
||||
|
||||
protected function beforeCreate(): bool {
|
||||
$currentCount = WarehouseOfferModel::count(['create' => ['from' => strtotime(date('Y-01-01'))]]);
|
||||
$this->postData['offerNumber'] = 'AN' . date('Y') . '-' . str_pad($currentCount + 1, 4, '0', STR_PAD_LEFT);
|
||||
$this->postData['status'] = 'new';
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -57,4 +50,24 @@ class WarehouseOfferController extends TTCrud {
|
||||
protected function getHistoryAction() {
|
||||
self::returnJson((new WarehouseHistoryController)->getHistory($this->request->id, $this->mod, $this->columns));
|
||||
}
|
||||
|
||||
protected function createTemplateAction() {
|
||||
$_POST = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$templateId = WarehouseOfferTemplateModel::create([
|
||||
'templateName' => $_POST['name'],
|
||||
'positions' => $_POST['positions'],
|
||||
'totalDiscount' => $_POST['totalDiscount'],
|
||||
'paymentTerms' => $_POST['paymentTerms'],
|
||||
'deliveryTerms' => $_POST['deliveryTerms'],
|
||||
'closingText' => $_POST['closingText'],
|
||||
'notes' => $_POST['notes'],
|
||||
]);
|
||||
|
||||
self::returnJson(['success' => true, 'id' => $templateId]);
|
||||
}
|
||||
|
||||
protected function getTemplatesAction() {
|
||||
self::returnJson(WarehouseOfferTemplateModel::getAll());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*
|
||||
* @property int $id Unique identifier for the warehouse offer
|
||||
* @property string $offerNumber Unique offer number
|
||||
* @property string $reference Reference number for the offer
|
||||
* @property string $customerNumber Customer number
|
||||
* @property string $customerName Name of the customer
|
||||
* @property string $customerStreet Street address of the customer
|
||||
@@ -30,6 +31,7 @@
|
||||
class WarehouseOfferModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public string $offerNumber;
|
||||
public string $reference;
|
||||
public string $customerNumber;
|
||||
public string $customerName;
|
||||
public string $customerStreet;
|
||||
@@ -50,3 +52,31 @@ class WarehouseOfferModel extends TTCrudBaseModel {
|
||||
public int $create;
|
||||
public int $createBy;
|
||||
}
|
||||
|
||||
//SQL TO CREATE TABLE
|
||||
/*
|
||||
CREATE TABLE `warehouse_offer` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`offerNumber` varchar(255) NOT NULL,
|
||||
`customerNumber` varchar(255) NOT NULL,
|
||||
`customerName` varchar(255) NOT NULL,
|
||||
`customerStreet` varchar(255) NOT NULL,
|
||||
`customerCity` varchar(255) NOT NULL,
|
||||
`customerZip` varchar(255) NOT NULL,
|
||||
`customerVAT` varchar(255) NOT NULL,
|
||||
`editor` int(11) NOT NULL,
|
||||
`purpose` varchar(255) NOT NULL,
|
||||
`positions` text NOT NULL,
|
||||
`alternativePositions` text NOT NULL,
|
||||
`totalDiscount` float NOT NULL,
|
||||
`paymentTerms` varchar(255) NOT NULL,
|
||||
`deliveryTerms` varchar(255) NOT NULL,
|
||||
`closingText` varchar(255) NOT NULL,
|
||||
`notes` varchar(255) NOT NULL,
|
||||
`status` varchar(255) NOT NULL,
|
||||
`totalAmount` float NOT NULL,
|
||||
`create` int(11) NOT NULL,
|
||||
`createBy` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @property mixed|null $name
|
||||
*/
|
||||
class WarehouseOfferTemplate extends mfBaseModel
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WarehouseOfferTemplateModel
|
||||
*
|
||||
* Represents a warehouse offer template with key details.
|
||||
*
|
||||
* @property string $templateName Name of the template
|
||||
* @property string $positions Details about positions in the offer
|
||||
* @property float $totalDiscount Total discount applied to the offer
|
||||
* @property string $paymentTerms Payment terms for the offer
|
||||
* @property string $deliveryTerms Delivery terms for the offer
|
||||
* @property string $closingText Closing text for the offer
|
||||
* @property string $notes Additional notes for the offer
|
||||
*/
|
||||
class WarehouseOfferTemplateModel extends TTCrudBaseModel
|
||||
{
|
||||
public string $templateName;
|
||||
public string $positions;
|
||||
public float $totalDiscount;
|
||||
public string $paymentTerms;
|
||||
public string $deliveryTerms;
|
||||
public string $closingText;
|
||||
public string $notes;
|
||||
}
|
||||
71
db/migrations/20250331090000_warehouse_modify_16.php
Normal file
71
db/migrations/20250331090000_warehouse_modify_16.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class WarehouseModify16 extends AbstractMigration {
|
||||
public function up(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
$WarehouseArticleTable = $this->table("WarehouseArticle");
|
||||
$WarehouseArticleTable
|
||||
->changeColumn("isEShop", "integer", ["default" => 0])
|
||||
->changeColumn("isEShopHide", "integer", ["default" => 0])
|
||||
->changeColumn("isSerialDocumentation", "integer", ["default" => 0])
|
||||
->save();
|
||||
|
||||
$WarehouseOfferTable = $this->table("WarehouseOffer", ["id" => false, "primary_key" => "id"]);
|
||||
$WarehouseOfferTable
|
||||
->addColumn("id", "integer", ["identity" => true])
|
||||
->addColumn("offerNumber", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("reference", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerNumber", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerName", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerStreet", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerCity", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerZip", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("customerVAT", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("editor", "integer", ["null" => false])
|
||||
->addColumn("purpose", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("positions", "text", ["null" => false])
|
||||
->addColumn("alternativePositions", "text", ["null" => false])
|
||||
->addColumn("totalDiscount", "float", ["null" => false])
|
||||
->addColumn("paymentTerms", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("deliveryTerms", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("closingText", "text", ["null" => false])
|
||||
->addColumn("notes", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("status", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("totalAmount", "float", ["null" => false])
|
||||
->addColumn("create", "integer", ["null" => false])
|
||||
->addColumn("createBy", "integer", ["null" => false])
|
||||
->save();
|
||||
|
||||
$WarehouseOfferTemplateTable = $this->table("WarehouseOfferTemplate", ["id" => false, "primary_key" => "id"]);
|
||||
$WarehouseOfferTemplateTable
|
||||
->addColumn("id", "integer", ["identity" => true])
|
||||
->addColumn("templateName", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("positions", "text", ["null" => false])
|
||||
->addColumn("totalDiscount", "float", ["null" => false])
|
||||
->addColumn("paymentTerms", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("deliveryTerms", "string", ["limit" => 255, "null" => false])
|
||||
->addColumn("closingText", "text", ["null" => false])
|
||||
->addColumn("notes", "text", ["null" => false])
|
||||
->save();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void {
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
// change table "WarehouseArticle" and set isEShop, isEShopHide, isSerialDocumentation to no default
|
||||
$WarehouseArticleTable = $this->table("WarehouseArticle");
|
||||
$WarehouseArticleTable
|
||||
->changeColumn("isEShop", "integer", ["default" => null])
|
||||
->changeColumn("isEShopHide", "integer", ["default" => null])
|
||||
->changeColumn("isSerialDocumentation", "integer", ["default" => null])
|
||||
->save();
|
||||
|
||||
$this->table("WarehouseOffer")->drop()->save();
|
||||
|
||||
$this->table("WarehouseOfferTemplate")->drop()->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,5 @@
|
||||
@media (min-width: 992px) {
|
||||
.modal-lg, .modal-xl {
|
||||
/*max width either 90% or 1120px*/
|
||||
max-width: min(90vw, 1120px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.warehouse-order-modal-positions-entry-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr !important;
|
||||
grid-gap: 10px;
|
||||
max-width: min(90vw) !important;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,73 @@
|
||||
Vue.component('warehouse-offer-modal', {
|
||||
props: {
|
||||
id: {type: [String, Number], required: true},
|
||||
props: {
|
||||
id: {type: [String, Number], required: true},
|
||||
mode: {type: String, default: 'edit'}
|
||||
},
|
||||
template: `
|
||||
<tt-modal :show="true"
|
||||
@submit="submit"
|
||||
:delete="id !== 'create'"
|
||||
:title="id === 'create' ? 'Angebot erstellen' : \`Angebot #\${id} bearbeiten\`"
|
||||
@update:show="$emit('close')">
|
||||
<div style="width: 99%"><h4 class="text-center">Angebotdetails</h4>
|
||||
<tt-select label="Sachbearbeiter"
|
||||
:options="window.TT_CONFIG.CRUD_CONFIG.columns.find(column => column.key === 'createBy')?.modal.items"
|
||||
sm
|
||||
row
|
||||
v-model="offer.editor"/>
|
||||
|
||||
<tt-autocomplete label="Kunde" v-model="offer.customerNumber" sm row :api-url="billAddrAutoCompleteUrl"/>
|
||||
<tt-input label="Kundenreferenz" v-model="offer.reference" sm row/>
|
||||
<tt-textarea label="Angebotszweck" v-model="offer.purpose" sm row/>
|
||||
<hr>
|
||||
<h4 class="text-center">Kundenadresse</h4>
|
||||
<div style="display: grid; grid-gap: 10px; grid-template-columns: 2fr 1fr 2fr 1fr 1fr;">
|
||||
<tt-input label="Name" v-model="offer.customerName" sm/>
|
||||
<tt-input label="Kontakt" v-model="offer.contactPerson" sm/>
|
||||
<tt-input label="Straße" v-model="offer.customerStreet" sm/>
|
||||
<tt-input label="PLZ" v-model="offer.customerZip" sm/>
|
||||
<tt-input label="Ort" v-model="offer.customerCity" sm/>
|
||||
</div>
|
||||
<hr>
|
||||
<h4 class="text-center">Positionen</h4>
|
||||
<tt-positions-manager group-mode ref="positionsManager" v-model="offer.positions" :config="positionsConfig" @updateField-article="fetchArticleData"/>
|
||||
<hr>
|
||||
<tt-input label="Gesamtrabatt (%)" v-model="offer.totalDiscount" sm row type="number"/>
|
||||
<tt-select label="Zahlungskonditionen" :options="paymentTerms" sm row v-model="offer.paymentTerms"/>
|
||||
<tt-select label="Lieferkonditionen" :options="deliveryTerms" sm row v-model="offer.deliveryTerms"/>
|
||||
<tt-textarea label="Schlusstext" sm rows="11" row v-model="offer.closingText"/>
|
||||
<hr>
|
||||
<tt-textarea label="Notizen" v-model="offer.notes" sm row/>
|
||||
</div>
|
||||
</tt-modal>
|
||||
`,
|
||||
<tt-modal :show="true"
|
||||
@submit="submit"
|
||||
:delete="id !== 'create'"
|
||||
:title="id === 'create' ? 'Angebot erstellen' : \`Angebot #\${id} bearbeiten\`"
|
||||
@update:show="$emit('close')">
|
||||
<div style="width: 99%"><h4 class="text-center">Angebotdetails</h4>
|
||||
<tt-select label="Sachbearbeiter"
|
||||
:options="window.TT_CONFIG.CRUD_CONFIG.columns.find(column => column.key === 'createBy')?.modal.items"
|
||||
sm
|
||||
row
|
||||
v-model="offer.editor"/>
|
||||
|
||||
<tt-autocomplete label="Kunde" v-model="offer.customerNumber" sm row :api-url="billAddrAutoCompleteUrl"/>
|
||||
<tt-input label="Kundenreferenz" v-model="offer.reference" sm row/>
|
||||
<tt-textarea label="Angebotszweck" v-model="offer.purpose" sm row/>
|
||||
<hr>
|
||||
<h4 class="text-center">Kundenadresse</h4>
|
||||
<div style="display: grid; grid-gap: 10px; grid-template-columns: 2fr 1fr 2fr 1fr 1fr 1fr;">
|
||||
<tt-input label="Name" v-model="offer.customerName" sm/>
|
||||
<tt-input label="Kontakt" v-model="offer.contactPerson" sm/>
|
||||
<tt-input label="Straße" v-model="offer.customerStreet" sm/>
|
||||
<tt-input label="PLZ" v-model="offer.customerZip" sm/>
|
||||
<tt-input label="Ort" v-model="offer.customerCity" sm/>
|
||||
<tt-input label="USt-IdNr." v-model="offer.customerVAT" sm/>
|
||||
</div>
|
||||
<hr>
|
||||
<h4 class="text-center">Positionen</h4>
|
||||
<tt-positions-manager group-mode ref="positionsManager" v-model="offer.positions" :config="positionsConfig"
|
||||
@updateField-article="fetchArticleData"/>
|
||||
<hr>
|
||||
<tt-input label="Gesamtrabatt (%)" v-model="offer.totalDiscount" sm row type="number"/>
|
||||
<tt-input label="Gesamtsumme" v-model="offerTotalPrice" sm row type="number" disabled/>
|
||||
<tt-select label="Zahlungskonditionen" :options="paymentTerms" sm row v-model="offer.paymentTerms"/>
|
||||
<tt-select label="Lieferkonditionen" :options="deliveryTerms" sm row v-model="offer.deliveryTerms"/>
|
||||
<tt-textarea label="Schlusstext" sm rows="11" row v-model="offer.closingText"/>
|
||||
<hr>
|
||||
<tt-textarea label="Notizen" v-model="offer.notes" sm row/>
|
||||
</div>
|
||||
|
||||
|
||||
<template v-slot:footer-prepend>
|
||||
<tt-input placeholder="Vorlagenname" no-form-group v-model="templateName"/>
|
||||
<tt-button text="Als Vorlage speichern" @click="saveTemplate" icon="fas fa-save" additional-class="btn-success"/>
|
||||
</template>
|
||||
</tt-modal>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
billAddrAutoCompleteUrl: window.TT_CONFIG['BASE_PATH'] + '/Address/Api?do=findAddress&fibu_primary_account=1',
|
||||
window: window,
|
||||
positionsConfig: {
|
||||
fields: {
|
||||
article: {
|
||||
type: 'autocomplete',
|
||||
label: 'Artikel',
|
||||
apiUrl: '/WarehouseArticle/autoComplete',
|
||||
window: window,
|
||||
positionsConfig: {
|
||||
fields: {
|
||||
article: {
|
||||
type: 'autocomplete',
|
||||
label: 'Artikel',
|
||||
apiUrl: '/WarehouseArticle/autoComplete',
|
||||
customFieldReference: 'WarehouseArticle',
|
||||
},
|
||||
amount: {type: 'input', label: 'Menge', inputType: 'number'},
|
||||
unit: {type: 'input', label: 'Einheit'},
|
||||
amount: {type: 'input', label: 'Menge', inputType: 'number'},
|
||||
unit: {type: 'input', label: 'Einheit'},
|
||||
articleNumber: {type: 'input', label: 'Artikelnummer'},
|
||||
isAlternative: {type: 'checkbox', label: 'Alternativposition'},
|
||||
unitPrice: {type: 'input', label: 'Einzelpreis', inputType: 'number'},
|
||||
discount: {type: 'input', label: 'Rabatt (%)', inputType: 'number'},
|
||||
unitPrice: {type: 'input', label: 'Einzelpreis', inputType: 'number'},
|
||||
discount: {type: 'input', label: 'Rabatt (%)', inputType: 'number'},
|
||||
},
|
||||
validateForm: (formData) => {
|
||||
const requiredFields = ['article', 'amount', 'unitPrice'];
|
||||
@@ -71,45 +80,46 @@ Vue.component('warehouse-offer-modal', {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
paymentTerms: [
|
||||
paymentTerms: [
|
||||
{value: 'net30', text: '30 Tage netto'},
|
||||
{value: 'net60', text: '60 Tage netto'},
|
||||
{value: 'immediate', text: 'Sofort fällig'},
|
||||
],
|
||||
deliveryTerms: [
|
||||
deliveryTerms: [
|
||||
{value: 'ex_works', text: 'Ab Werk'},
|
||||
{value: 'free_delivery', text: 'Frei Haus'},
|
||||
{value: 'fob', text: 'FOB'},
|
||||
],
|
||||
offer: {
|
||||
editor: window.TT_CONFIG['USER_ID'],
|
||||
customerNumber: '',
|
||||
reference: '',
|
||||
purpose: '',
|
||||
customerName: '',
|
||||
customerStreet: '',
|
||||
customerZip: '',
|
||||
customerCity: '',
|
||||
customerVAT: '',
|
||||
positions: [],
|
||||
offer: {
|
||||
editor: window.TT_CONFIG['USER_ID'],
|
||||
customerNumber: '',
|
||||
reference: '',
|
||||
purpose: '',
|
||||
customerName: '',
|
||||
customerStreet: '',
|
||||
customerZip: '',
|
||||
customerCity: '',
|
||||
customerVAT: '',
|
||||
positions: [],
|
||||
alternativePositions: [],
|
||||
totalDiscount: 0,
|
||||
paymentTerms: 'net30',
|
||||
deliveryTerms: 'ex_works',
|
||||
closingText: 'Sollten sich die Rohstoffpreise bzw. die Preise unserer Zulieferer um mehr als 10% innerhalb der Angebots bzw.\n' +
|
||||
'\n' +
|
||||
'Auftragsgültigkeit erhöhen (Stichtag Datum), sind wir gezwungen die Preise anzupassen.\n' +
|
||||
'\n' +
|
||||
'Diese Angebot hat eine Gültigkeit von 4 Wochen.\n' +
|
||||
'\n' +
|
||||
'Verrechnung erfolgt nach tatsächlichem Aufwand.\n' +
|
||||
'\n' +
|
||||
'Wir sind sicher, Ihnen ein konkurenzfähiges Angebot unterbreitet zu haben und sehen gern Ihrer Bestellung entgegen.\n' +
|
||||
'\n' +
|
||||
'Sollten Sie noch Fragen oder weitere Informationen benötigen stehen wir Ihnen jederzeit gern zu Verfügung.\n' +
|
||||
' ',
|
||||
notes: '',
|
||||
}
|
||||
totalDiscount: 0,
|
||||
paymentTerms: 'net30',
|
||||
deliveryTerms: 'ex_works',
|
||||
closingText: 'Sollten sich die Rohstoffpreise bzw. die Preise unserer Zulieferer um mehr als 10% innerhalb der Angebots bzw.\n' +
|
||||
'\n' +
|
||||
'Auftragsgültigkeit erhöhen (Stichtag Datum), sind wir gezwungen die Preise anzupassen.\n' +
|
||||
'\n' +
|
||||
'Diese Angebot hat eine Gültigkeit von 4 Wochen.\n' +
|
||||
'\n' +
|
||||
'Verrechnung erfolgt nach tatsächlichem Aufwand.\n' +
|
||||
'\n' +
|
||||
'Wir sind sicher, Ihnen ein konkurenzfähiges Angebot unterbreitet zu haben und sehen gern Ihrer Bestellung entgegen.\n' +
|
||||
'\n' +
|
||||
'Sollten Sie noch Fragen oder weitere Informationen benötigen stehen wir Ihnen jederzeit gern zu Verfügung.\n' +
|
||||
' ',
|
||||
notes: '',
|
||||
},
|
||||
templateName: '',
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -122,6 +132,7 @@ Vue.component('warehouse-offer-modal', {
|
||||
},
|
||||
methods: {
|
||||
async submit() {
|
||||
this.offer.totalAmount = this.offerTotalPrice;
|
||||
if (this.offer.positions.length === 0) return window.notify('error', 'Bitte fügen Sie mindestens eine Position hinzu.');
|
||||
|
||||
const url = this.id === 'create'
|
||||
@@ -147,32 +158,107 @@ Vue.component('warehouse-offer-modal', {
|
||||
this.$refs.positionsManager.updateField('unit', response.data.unit);
|
||||
}
|
||||
},
|
||||
async saveTemplate() {
|
||||
if (!this.templateName) return window.notify('error', 'Bitte geben Sie einen Namen für die Vorlage ein.');
|
||||
if (this.offer.positions.length === 0) return window.notify('error', 'Bitte fügen Sie mindestens eine Position hinzu.');
|
||||
|
||||
const response = await axios.post(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/createTemplate`, {
|
||||
name: this.templateName,
|
||||
positions: this.offer.positions,
|
||||
totalDiscount: this.offer.totalDiscount,
|
||||
paymentTerms: this.offer.paymentTerms,
|
||||
deliveryTerms: this.offer.deliveryTerms,
|
||||
closingText: this.offer.closingText,
|
||||
notes: this.offer.notes
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
window.notify('success', response.data.message ?? 'Vorlage erfolgreich gespeichert');
|
||||
} else {
|
||||
window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten');
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'offer.customerNumber': async function () {
|
||||
if (!this.offer.customerNumber) return;
|
||||
|
||||
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/Address/api?do=getAddress&id=${this.offer.customerNumber}`);
|
||||
if (response.data.status !== 'OK' || !response.data.result.address) {
|
||||
this.window.notify('error', 'Kundenadresse konnte nicht gefunden werden');
|
||||
return;
|
||||
}
|
||||
|
||||
const address = response.data.result.address;
|
||||
this.offer.customerName = address.company || `${address.firstname} ${address.lastname}`;
|
||||
this.offer.customerStreet = address.street;
|
||||
this.offer.customerZip = address.zip;
|
||||
this.offer.customerCity = address.city;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
offerTotalPrice() {
|
||||
const totalPrice = this.offer.positions.reduce((total, position) => {
|
||||
if (!position.amount) return total;
|
||||
const discount = position.discount ? (position.unitPrice * position.amount) * position.discount / 100 : 0;
|
||||
return total + (position.unitPrice * position.amount) - discount;
|
||||
}, 0);
|
||||
|
||||
return totalPrice - (totalPrice * this.offer.totalDiscount / 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Vue.component('warehouse-offer', {
|
||||
template: `
|
||||
<tt-card>
|
||||
<warehouse-offer-modal v-if="offerModalId" :id="offerModalId" @close="offerModalId = null;$refs.table.$refs.table.refreshTable()"/>
|
||||
<button @click="offerModalId = 'create'" class="btn btn-primary">Angebot erstellen</button>
|
||||
<tt-table-crud emit-edit @edit="offerModalId = $event.id" ref="table">
|
||||
<template v-slot:expandedRow="{ row }">
|
||||
<div>
|
||||
<h5>Notizen</h5>
|
||||
<p>{{ row.notes }}</p>
|
||||
<h5>Verlauf</h5>
|
||||
<ul>
|
||||
<li v-for="entry in row.journal">{{ entry.date }} - {{ entry.description }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</tt-table-crud>
|
||||
</tt-card>
|
||||
`,
|
||||
<tt-card>
|
||||
<warehouse-offer-modal v-if="offerModalId" :id="offerModalId" ref="modal"
|
||||
@close="offerModalId = null;$refs.table.$refs.table.refreshTable()"/>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<button @click="offerModalId = 'create'" class="btn btn-primary">Angebot erstellen</button>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-primary dropdown-toggle" @click="offerTemplatesDropdown = !offerTemplatesDropdown">
|
||||
Angebot aus Vorlage erstellen <i class="fas fa-caret-down"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu" :class="{'show': offerTemplatesDropdown}">
|
||||
<li v-for="template in offerTemplates" @click="createOfferFromTemplate(template)">
|
||||
<a class="dropdown-item">{{ template.templateName }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<tt-table-crud emit-edit @edit="offerModalId = $event.id" ref="table">
|
||||
</tt-table-crud>
|
||||
</tt-card>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
window: window,
|
||||
window: window,
|
||||
offerModalId: null,
|
||||
offerTemplates: [],
|
||||
offerTemplatesDropdown: false,
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
const response = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOffer/getTemplates`);
|
||||
this.offerTemplates = response.data;
|
||||
},
|
||||
methods: {
|
||||
async createOfferFromTemplate(template) {
|
||||
this.offerModalId = 'create';
|
||||
await this.$nextTick();
|
||||
|
||||
this.$refs.modal.offer.positions = JSON.parse(template.positions);
|
||||
this.$refs.modal.offer.totalDiscount = template.totalDiscount;
|
||||
this.$refs.modal.offer.paymentTerms = template.paymentTerms;
|
||||
this.$refs.modal.offer.deliveryTerms = template.deliveryTerms;
|
||||
this.$refs.modal.offer.closingText = template.closingText;
|
||||
this.$refs.modal.offer.notes = template.notes;
|
||||
|
||||
this.window.notify('success', 'Angebot aus Vorlage erstellt');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ Vue.component('tt-input', {
|
||||
hint: String,
|
||||
additionalProps: Object,
|
||||
sm: {type: Boolean, default: false},
|
||||
noFormGroup: {type: Boolean, default: false},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -22,7 +23,7 @@ Vue.component('tt-input', {
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="form-group" :class="{'row': row}">
|
||||
<div :class="{'row': row, 'form-group' : !noFormGroup}">
|
||||
<slot name="prepend"></slot>
|
||||
<label
|
||||
:class="{'col-form-label': row, 'col-sm-4': row, 'col-form-label-sm': sm && row}"
|
||||
|
||||
Reference in New Issue
Block a user