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">
|
<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 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") || $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="--><?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="<?=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="--><?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 (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("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; ?>
|
<?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 {
|
class WarehouseOfferController extends TTCrud {
|
||||||
protected string $headerTitle = 'Angebote';
|
protected string $headerTitle = 'Angebote';
|
||||||
|
protected string $singleText = 'Angebot';
|
||||||
protected bool $createText = false;
|
protected bool $createText = false;
|
||||||
|
|
||||||
protected array $columns = [
|
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' => 'offerNumber', 'text' => 'Angebotsnummer', 'required' => true, 'modal' => false],
|
||||||
['key' => 'customerNumber', 'text' => 'Kundennummer', 'required' => true, 'modal' => false],
|
['key' => 'customerNumber', 'text' => 'Kundennummer', 'required' => true, 'modal' => false],
|
||||||
['key' => 'customerName', 'text' => 'Kundenname', 'required' => true, 'modal' => false],
|
['key' => 'customerName', 'text' => 'Kundenname', 'required' => true, 'modal' => false],
|
||||||
['key' => 'customerCity', 'text' => 'Stadt', 'required' => true, 'modal' => false],
|
['key' => 'customerCity', 'text' => 'Stadt', 'required' => true, 'modal' => false],
|
||||||
['key' => 'customerVAT', 'text' => 'UID', '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' => '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' => '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',
|
['key' => 'actions',
|
||||||
'text' => 'Aktionen',
|
'text' => 'Aktionen',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
@@ -24,27 +25,19 @@ class WarehouseOfferController extends TTCrud {
|
|||||||
];
|
];
|
||||||
|
|
||||||
protected array $permissionCheck = ['WarehouseAdmin'];
|
protected array $permissionCheck = ['WarehouseAdmin'];
|
||||||
|
protected array $additionalActions = [['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary']];
|
||||||
|
|
||||||
protected array $additionalActions = [
|
protected function prepareCrudConfig(): void {
|
||||||
['key' => 'openHistory', 'title' => 'Historie', 'class' => 'fas fa-history text-primary'],
|
$editorColumnIndex = array_search('editor', array_column($this->columns, 'key'));
|
||||||
['key' => 'sendOffer', 'title' => 'Angebot senden', 'class' => 'fas fa-paper-plane text-success']
|
$this->columns[$editorColumnIndex]['modal']['items'] = array_map(function ($user) {
|
||||||
];
|
return ['value' => intval($user->id), 'text' => $user->name];
|
||||||
|
}, UserModel::search(['employee' => true]));
|
||||||
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 beforeCreate(): bool {
|
protected function beforeCreate(): bool {
|
||||||
$currentCount = WarehouseOfferModel::count(['create' => ['from' => strtotime(date('Y-01-01'))]]);
|
$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['offerNumber'] = 'AN' . date('Y') . '-' . str_pad($currentCount + 1, 4, '0', STR_PAD_LEFT);
|
||||||
|
$this->postData['status'] = 'new';
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -57,4 +50,24 @@ class WarehouseOfferController extends TTCrud {
|
|||||||
protected function getHistoryAction() {
|
protected function getHistoryAction() {
|
||||||
self::returnJson((new WarehouseHistoryController)->getHistory($this->request->id, $this->mod, $this->columns));
|
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 int $id Unique identifier for the warehouse offer
|
||||||
* @property string $offerNumber Unique offer number
|
* @property string $offerNumber Unique offer number
|
||||||
|
* @property string $reference Reference number for the offer
|
||||||
* @property string $customerNumber Customer number
|
* @property string $customerNumber Customer number
|
||||||
* @property string $customerName Name of the customer
|
* @property string $customerName Name of the customer
|
||||||
* @property string $customerStreet Street address of the customer
|
* @property string $customerStreet Street address of the customer
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
class WarehouseOfferModel extends TTCrudBaseModel {
|
class WarehouseOfferModel extends TTCrudBaseModel {
|
||||||
public int $id;
|
public int $id;
|
||||||
public string $offerNumber;
|
public string $offerNumber;
|
||||||
|
public string $reference;
|
||||||
public string $customerNumber;
|
public string $customerNumber;
|
||||||
public string $customerName;
|
public string $customerName;
|
||||||
public string $customerStreet;
|
public string $customerStreet;
|
||||||
@@ -50,3 +52,31 @@ class WarehouseOfferModel extends TTCrudBaseModel {
|
|||||||
public int $create;
|
public int $create;
|
||||||
public int $createBy;
|
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;
|
||||||
|
}
|
||||||
@@ -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) {
|
@media (min-width: 992px) {
|
||||||
.modal-lg, .modal-xl {
|
.modal-lg, .modal-xl {
|
||||||
/*max width either 90% or 1120px*/
|
max-width: min(90vw) !important;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,64 +1,73 @@
|
|||||||
Vue.component('warehouse-offer-modal', {
|
Vue.component('warehouse-offer-modal', {
|
||||||
props: {
|
props: {
|
||||||
id: {type: [String, Number], required: true},
|
id: {type: [String, Number], required: true},
|
||||||
mode: {type: String, default: 'edit'}
|
mode: {type: String, default: 'edit'}
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<tt-modal :show="true"
|
<tt-modal :show="true"
|
||||||
@submit="submit"
|
@submit="submit"
|
||||||
:delete="id !== 'create'"
|
:delete="id !== 'create'"
|
||||||
:title="id === 'create' ? 'Angebot erstellen' : \`Angebot #\${id} bearbeiten\`"
|
:title="id === 'create' ? 'Angebot erstellen' : \`Angebot #\${id} bearbeiten\`"
|
||||||
@update:show="$emit('close')">
|
@update:show="$emit('close')">
|
||||||
<div style="width: 99%"><h4 class="text-center">Angebotdetails</h4>
|
<div style="width: 99%"><h4 class="text-center">Angebotdetails</h4>
|
||||||
<tt-select label="Sachbearbeiter"
|
<tt-select label="Sachbearbeiter"
|
||||||
:options="window.TT_CONFIG.CRUD_CONFIG.columns.find(column => column.key === 'createBy')?.modal.items"
|
:options="window.TT_CONFIG.CRUD_CONFIG.columns.find(column => column.key === 'createBy')?.modal.items"
|
||||||
sm
|
sm
|
||||||
row
|
row
|
||||||
v-model="offer.editor"/>
|
v-model="offer.editor"/>
|
||||||
|
|
||||||
<tt-autocomplete label="Kunde" v-model="offer.customerNumber" sm row :api-url="billAddrAutoCompleteUrl"/>
|
<tt-autocomplete label="Kunde" v-model="offer.customerNumber" sm row :api-url="billAddrAutoCompleteUrl"/>
|
||||||
<tt-input label="Kundenreferenz" v-model="offer.reference" sm row/>
|
<tt-input label="Kundenreferenz" v-model="offer.reference" sm row/>
|
||||||
<tt-textarea label="Angebotszweck" v-model="offer.purpose" sm row/>
|
<tt-textarea label="Angebotszweck" v-model="offer.purpose" sm row/>
|
||||||
<hr>
|
<hr>
|
||||||
<h4 class="text-center">Kundenadresse</h4>
|
<h4 class="text-center">Kundenadresse</h4>
|
||||||
<div style="display: grid; grid-gap: 10px; grid-template-columns: 2fr 1fr 2fr 1fr 1fr;">
|
<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="Name" v-model="offer.customerName" sm/>
|
||||||
<tt-input label="Kontakt" v-model="offer.contactPerson" sm/>
|
<tt-input label="Kontakt" v-model="offer.contactPerson" sm/>
|
||||||
<tt-input label="Straße" v-model="offer.customerStreet" sm/>
|
<tt-input label="Straße" v-model="offer.customerStreet" sm/>
|
||||||
<tt-input label="PLZ" v-model="offer.customerZip" sm/>
|
<tt-input label="PLZ" v-model="offer.customerZip" sm/>
|
||||||
<tt-input label="Ort" v-model="offer.customerCity" sm/>
|
<tt-input label="Ort" v-model="offer.customerCity" sm/>
|
||||||
</div>
|
<tt-input label="USt-IdNr." v-model="offer.customerVAT" sm/>
|
||||||
<hr>
|
</div>
|
||||||
<h4 class="text-center">Positionen</h4>
|
<hr>
|
||||||
<tt-positions-manager group-mode ref="positionsManager" v-model="offer.positions" :config="positionsConfig" @updateField-article="fetchArticleData"/>
|
<h4 class="text-center">Positionen</h4>
|
||||||
<hr>
|
<tt-positions-manager group-mode ref="positionsManager" v-model="offer.positions" :config="positionsConfig"
|
||||||
<tt-input label="Gesamtrabatt (%)" v-model="offer.totalDiscount" sm row type="number"/>
|
@updateField-article="fetchArticleData"/>
|
||||||
<tt-select label="Zahlungskonditionen" :options="paymentTerms" sm row v-model="offer.paymentTerms"/>
|
<hr>
|
||||||
<tt-select label="Lieferkonditionen" :options="deliveryTerms" sm row v-model="offer.deliveryTerms"/>
|
<tt-input label="Gesamtrabatt (%)" v-model="offer.totalDiscount" sm row type="number"/>
|
||||||
<tt-textarea label="Schlusstext" sm rows="11" row v-model="offer.closingText"/>
|
<tt-input label="Gesamtsumme" v-model="offerTotalPrice" sm row type="number" disabled/>
|
||||||
<hr>
|
<tt-select label="Zahlungskonditionen" :options="paymentTerms" sm row v-model="offer.paymentTerms"/>
|
||||||
<tt-textarea label="Notizen" v-model="offer.notes" sm row/>
|
<tt-select label="Lieferkonditionen" :options="deliveryTerms" sm row v-model="offer.deliveryTerms"/>
|
||||||
</div>
|
<tt-textarea label="Schlusstext" sm rows="11" row v-model="offer.closingText"/>
|
||||||
</tt-modal>
|
<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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
billAddrAutoCompleteUrl: window.TT_CONFIG['BASE_PATH'] + '/Address/Api?do=findAddress&fibu_primary_account=1',
|
billAddrAutoCompleteUrl: window.TT_CONFIG['BASE_PATH'] + '/Address/Api?do=findAddress&fibu_primary_account=1',
|
||||||
window: window,
|
window: window,
|
||||||
positionsConfig: {
|
positionsConfig: {
|
||||||
fields: {
|
fields: {
|
||||||
article: {
|
article: {
|
||||||
type: 'autocomplete',
|
type: 'autocomplete',
|
||||||
label: 'Artikel',
|
label: 'Artikel',
|
||||||
apiUrl: '/WarehouseArticle/autoComplete',
|
apiUrl: '/WarehouseArticle/autoComplete',
|
||||||
customFieldReference: 'WarehouseArticle',
|
customFieldReference: 'WarehouseArticle',
|
||||||
},
|
},
|
||||||
amount: {type: 'input', label: 'Menge', inputType: 'number'},
|
amount: {type: 'input', label: 'Menge', inputType: 'number'},
|
||||||
unit: {type: 'input', label: 'Einheit'},
|
unit: {type: 'input', label: 'Einheit'},
|
||||||
articleNumber: {type: 'input', label: 'Artikelnummer'},
|
articleNumber: {type: 'input', label: 'Artikelnummer'},
|
||||||
isAlternative: {type: 'checkbox', label: 'Alternativposition'},
|
isAlternative: {type: 'checkbox', label: 'Alternativposition'},
|
||||||
unitPrice: {type: 'input', label: 'Einzelpreis', inputType: 'number'},
|
unitPrice: {type: 'input', label: 'Einzelpreis', inputType: 'number'},
|
||||||
discount: {type: 'input', label: 'Rabatt (%)', inputType: 'number'},
|
discount: {type: 'input', label: 'Rabatt (%)', inputType: 'number'},
|
||||||
},
|
},
|
||||||
validateForm: (formData) => {
|
validateForm: (formData) => {
|
||||||
const requiredFields = ['article', 'amount', 'unitPrice'];
|
const requiredFields = ['article', 'amount', 'unitPrice'];
|
||||||
@@ -71,45 +80,46 @@ Vue.component('warehouse-offer-modal', {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
paymentTerms: [
|
paymentTerms: [
|
||||||
{value: 'net30', text: '30 Tage netto'},
|
{value: 'net30', text: '30 Tage netto'},
|
||||||
{value: 'net60', text: '60 Tage netto'},
|
{value: 'net60', text: '60 Tage netto'},
|
||||||
{value: 'immediate', text: 'Sofort fällig'},
|
{value: 'immediate', text: 'Sofort fällig'},
|
||||||
],
|
],
|
||||||
deliveryTerms: [
|
deliveryTerms: [
|
||||||
{value: 'ex_works', text: 'Ab Werk'},
|
{value: 'ex_works', text: 'Ab Werk'},
|
||||||
{value: 'free_delivery', text: 'Frei Haus'},
|
{value: 'free_delivery', text: 'Frei Haus'},
|
||||||
{value: 'fob', text: 'FOB'},
|
{value: 'fob', text: 'FOB'},
|
||||||
],
|
],
|
||||||
offer: {
|
offer: {
|
||||||
editor: window.TT_CONFIG['USER_ID'],
|
editor: window.TT_CONFIG['USER_ID'],
|
||||||
customerNumber: '',
|
customerNumber: '',
|
||||||
reference: '',
|
reference: '',
|
||||||
purpose: '',
|
purpose: '',
|
||||||
customerName: '',
|
customerName: '',
|
||||||
customerStreet: '',
|
customerStreet: '',
|
||||||
customerZip: '',
|
customerZip: '',
|
||||||
customerCity: '',
|
customerCity: '',
|
||||||
customerVAT: '',
|
customerVAT: '',
|
||||||
positions: [],
|
positions: [],
|
||||||
alternativePositions: [],
|
alternativePositions: [],
|
||||||
totalDiscount: 0,
|
totalDiscount: 0,
|
||||||
paymentTerms: 'net30',
|
paymentTerms: 'net30',
|
||||||
deliveryTerms: 'ex_works',
|
deliveryTerms: 'ex_works',
|
||||||
closingText: 'Sollten sich die Rohstoffpreise bzw. die Preise unserer Zulieferer um mehr als 10% innerhalb der Angebots bzw.\n' +
|
closingText: 'Sollten sich die Rohstoffpreise bzw. die Preise unserer Zulieferer um mehr als 10% innerhalb der Angebots bzw.\n' +
|
||||||
'\n' +
|
'\n' +
|
||||||
'Auftragsgültigkeit erhöhen (Stichtag Datum), sind wir gezwungen die Preise anzupassen.\n' +
|
'Auftragsgültigkeit erhöhen (Stichtag Datum), sind wir gezwungen die Preise anzupassen.\n' +
|
||||||
'\n' +
|
'\n' +
|
||||||
'Diese Angebot hat eine Gültigkeit von 4 Wochen.\n' +
|
'Diese Angebot hat eine Gültigkeit von 4 Wochen.\n' +
|
||||||
'\n' +
|
'\n' +
|
||||||
'Verrechnung erfolgt nach tatsächlichem Aufwand.\n' +
|
'Verrechnung erfolgt nach tatsächlichem Aufwand.\n' +
|
||||||
'\n' +
|
'\n' +
|
||||||
'Wir sind sicher, Ihnen ein konkurenzfähiges Angebot unterbreitet zu haben und sehen gern Ihrer Bestellung entgegen.\n' +
|
'Wir sind sicher, Ihnen ein konkurenzfähiges Angebot unterbreitet zu haben und sehen gern Ihrer Bestellung entgegen.\n' +
|
||||||
'\n' +
|
'\n' +
|
||||||
'Sollten Sie noch Fragen oder weitere Informationen benötigen stehen wir Ihnen jederzeit gern zu Verfügung.\n' +
|
'Sollten Sie noch Fragen oder weitere Informationen benötigen stehen wir Ihnen jederzeit gern zu Verfügung.\n' +
|
||||||
' ',
|
' ',
|
||||||
notes: '',
|
notes: '',
|
||||||
}
|
},
|
||||||
|
templateName: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -122,6 +132,7 @@ Vue.component('warehouse-offer-modal', {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async submit() {
|
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.');
|
if (this.offer.positions.length === 0) return window.notify('error', 'Bitte fügen Sie mindestens eine Position hinzu.');
|
||||||
|
|
||||||
const url = this.id === 'create'
|
const url = this.id === 'create'
|
||||||
@@ -147,32 +158,107 @@ Vue.component('warehouse-offer-modal', {
|
|||||||
this.$refs.positionsManager.updateField('unit', response.data.unit);
|
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', {
|
Vue.component('warehouse-offer', {
|
||||||
template: `
|
template: `
|
||||||
<tt-card>
|
<tt-card>
|
||||||
<warehouse-offer-modal v-if="offerModalId" :id="offerModalId" @close="offerModalId = null;$refs.table.$refs.table.refreshTable()"/>
|
<warehouse-offer-modal v-if="offerModalId" :id="offerModalId" ref="modal"
|
||||||
<button @click="offerModalId = 'create'" class="btn btn-primary">Angebot erstellen</button>
|
@close="offerModalId = null;$refs.table.$refs.table.refreshTable()"/>
|
||||||
<tt-table-crud emit-edit @edit="offerModalId = $event.id" ref="table">
|
<div style="display: flex; gap: 8px">
|
||||||
<template v-slot:expandedRow="{ row }">
|
<button @click="offerModalId = 'create'" class="btn btn-primary">Angebot erstellen</button>
|
||||||
<div>
|
<div class="dropdown">
|
||||||
<h5>Notizen</h5>
|
<button class="btn btn-outline-primary dropdown-toggle" @click="offerTemplatesDropdown = !offerTemplatesDropdown">
|
||||||
<p>{{ row.notes }}</p>
|
Angebot aus Vorlage erstellen <i class="fas fa-caret-down"></i>
|
||||||
<h5>Verlauf</h5>
|
</button>
|
||||||
<ul>
|
<ul class="dropdown-menu" :class="{'show': offerTemplatesDropdown}">
|
||||||
<li v-for="entry in row.journal">{{ entry.date }} - {{ entry.description }}</li>
|
<li v-for="template in offerTemplates" @click="createOfferFromTemplate(template)">
|
||||||
</ul>
|
<a class="dropdown-item">{{ template.templateName }}</a>
|
||||||
</div>
|
</li>
|
||||||
</template>
|
</ul>
|
||||||
</tt-table-crud>
|
</div>
|
||||||
</tt-card>
|
</div>
|
||||||
`,
|
|
||||||
|
|
||||||
|
|
||||||
|
<tt-table-crud emit-edit @edit="offerModalId = $event.id" ref="table">
|
||||||
|
</tt-table-crud>
|
||||||
|
</tt-card>
|
||||||
|
`,
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
window: window,
|
window: window,
|
||||||
offerModalId: null,
|
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,
|
hint: String,
|
||||||
additionalProps: Object,
|
additionalProps: Object,
|
||||||
sm: {type: Boolean, default: false},
|
sm: {type: Boolean, default: false},
|
||||||
|
noFormGroup: {type: Boolean, default: false},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -22,7 +23,7 @@ Vue.component('tt-input', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<div class="form-group" :class="{'row': row}">
|
<div :class="{'row': row, 'form-group' : !noFormGroup}">
|
||||||
<slot name="prepend"></slot>
|
<slot name="prepend"></slot>
|
||||||
<label
|
<label
|
||||||
:class="{'col-form-label': row, 'col-sm-4': row, 'col-form-label-sm': sm && row}"
|
:class="{'col-form-label': row, 'col-sm-4': row, 'col-form-label-sm': sm && row}"
|
||||||
|
|||||||
Reference in New Issue
Block a user