Project margin updater completed using GeneralSettings master
authorVásáry Dániel <vasary@elgekko.net>
Thu, 7 Mar 2024 15:26:14 +0000 (16:26 +0100)
committerVásáry Dániel <vasary@elgekko.net>
Thu, 7 Mar 2024 15:26:14 +0000 (16:26 +0100)
14 files changed:
sly-crm-db/migrations/scripts/032_insert_general_settings.sql [new file with mode: 0644]
sly-crm-ui/src/main/java/hu/user/lis/ui/auth/CurrentProfile.java
sly-crm-ui/src/main/java/hu/user/lis/ui/data/IncomeMarginsDataModel.java
sly-crm-ui/src/main/java/hu/user/lis/ui/data/MoneyBalance.java
sly-crm-ui/src/main/java/hu/user/lis/ui/data/common/ObjectDictionary.java [new file with mode: 0644]
sly-crm-ui/src/main/java/hu/user/lis/ui/editor/ExchangedRateValueUpdater.java
sly-crm-ui/src/main/java/hu/user/lis/ui/editor/InvoiceEditorModel.java
sly-crm-ui/src/main/java/hu/user/lis/ui/editor/ProjectEditorModel.java
sly-crm-ui/src/main/java/hu/user/lis/ui/editor/ServiceRecordEditorModel.java
sly-crm-ui/src/main/java/hu/user/lis/ui/editor/common/EntityEditorModel.java
sly-crm-ui/src/main/java/hu/user/lis/ui/properties/GeneralSettings.java [new file with mode: 0644]
sly-crm-ui/src/main/java/hu/user/lis/ui/view/SettingsViewModel.java
sly-crm-ui/src/main/resources/web/editor/project-editor.zul
sly-crm-ui/src/main/resources/web/settings.zul

diff --git a/sly-crm-db/migrations/scripts/032_insert_general_settings.sql b/sly-crm-db/migrations/scripts/032_insert_general_settings.sql
new file mode 100644 (file)
index 0000000..62ee4e2
--- /dev/null
@@ -0,0 +1,9 @@
+-- // add price exchange fields to treasury
+-- Migration SQL that makes the change goes here.
+
+INSERT INTO profile (type, setting) VALUES ('General', '{"autoProjectMarginUpdate": true}');
+
+-- //@UNDO
+-- SQL to undo the change goes here.
+
+DELETE FROM profile WHERE type="General";
index bef741a0f8e0015576dc39e8becebdb2da0ec10a..4eb0f83e3f23983d093b7d237c8431603a43ba57 100644 (file)
@@ -3,7 +3,10 @@ package hu.user.lis.ui.auth;
 import hu.user.lis.db.Associate;
 import hu.user.lis.db.Profile;
 import hu.user.lis.db.repository.ProfileRepository;
+import hu.user.lis.service.data.EntityDataService;
+import hu.user.lis.ui.data.common.ObjectDictionary;
 import hu.user.lis.ui.session.SessionSettings;
+import lombok.Getter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -17,25 +20,40 @@ public class CurrentProfile {
 
     public static final String ASSOCIATE = "ASSOCIATE";
     public static final String SETTINGS = "SETTINGS";
+
     @Autowired
     private ProfileRepository profileRepository;
 
     @Autowired
     private SessionSettings sessionSettings;
 
+    @Getter
+    private ObjectDictionary generalSettings;
+
+    @Autowired
+    private EntityDataService<ObjectDictionary> entityDataService;
 
     public Associate getAssociate() {
         return (Associate) sessionSettings.getAttribute(ASSOCIATE);
     }
 
-//    @Getter
-//    Map<String, Profile> settings = new HashMap<>();
 
     public void setAssociate(Associate associate) {
         sessionSettings.setAttribute(ASSOCIATE, associate);
+        profileRepository.findByLoginAndType(null, "General").ifPresent(profile -> {
+            generalSettings = entityDataService.fromJSON(profile.getSetting(), ObjectDictionary.class);
+        });
         loadSettings();
     }
 
+    public void saveGeneralSettings() {
+        profileRepository.findByLoginAndType(null, "General").ifPresent(profile -> {
+            String setting = entityDataService.toJSON(generalSettings);
+            profile.setSetting(setting);
+            profileRepository.save(profile);
+        });
+    }
+
     private void loadSettings() {
         if (Objects.isNull(getAssociate())) {
             return;
index e58ed2dd05cde2412654e582580772bf13efdf02..3017fe2002d2d2b637086e8f48a6f10f7940908d 100644 (file)
@@ -7,7 +7,10 @@ import hu.user.lis.db.OutgoingInvoice;
 import hu.user.lis.db.Project;
 import hu.user.lis.db.ServiceRecord;
 import hu.user.lis.db.Treasury;
+import hu.user.lis.ui.editor.ExchangedRateValueUpdater;
+import hu.user.lis.ui.properties.GeneralSettings;
 import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
@@ -17,44 +20,75 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 
 @Component
 @Log4j2
 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
 public class IncomeMarginsDataModel extends ArrayList<MoneyAmount> {
+    @Autowired
+    private GeneralSettings generalSettings;
+
+    @Autowired
+    private ExchangedRateValueUpdater exchangedRateValueUpdater;
+
     public void recalculate(Project project, ServiceRecordsDataModel serviceRecordsDataModel) throws Exception {
+        boolean autoUpdateProjectMargin = generalSettings.getAutoProjectMarginUpdate();
+
+        MoneyBalance projectHUFBalance = MoneyBalance.builder().build();
         clear();
+
         Map<Currency, MoneyAmount> balances = new HashMap<>();
         List<OutgoingInvoice> outgoingInvoices = project.getOutgoingInvoices();
-        if (Objects.nonNull(outgoingInvoices)) {
-            for (Invoice invoice : outgoingInvoices) {
-                addBalance(balances, MoneyAmount.fromInvoice(invoice));
+        for (Invoice invoice : outgoingInvoices) {
+            addBalance(balances, MoneyAmount.fromInvoice(invoice));
+            if (autoUpdateProjectMargin) {
+                double income = exchangedRateValueUpdater.updateInvoiceExchangedValues(invoice);
+                projectHUFBalance.addIncome(income);
             }
         }
 
         List<IncomingInvoice> incomingInvoices = project.getIncomingInvoices();
-        if (Objects.nonNull(incomingInvoices)) {
-            for (Invoice invoice : incomingInvoices) {
-                subtractBalance(balances, MoneyAmount.fromInvoice(invoice));
+        for (Invoice invoice : incomingInvoices) {
+            subtractBalance(balances, MoneyAmount.fromInvoice(invoice));
+            if (autoUpdateProjectMargin) {
+                double expense = exchangedRateValueUpdater.updateInvoiceExchangedValues(invoice);
+                projectHUFBalance.addExpense(expense);
             }
         }
 
         List<Treasury> treasuries = project.getTreasuries();
-        if (Objects.nonNull(treasuries)) {
-            for (Treasury treasury : treasuries) {
-                subtractBalance(balances, MoneyAmount.fromTreasuryBuy(treasury));
-                addBalance(balances, MoneyAmount.fromTreasurySell(treasury));
+        for (Treasury treasury : treasuries) {
+            subtractBalance(balances, MoneyAmount.fromTreasuryBuy(treasury));
+            addBalance(balances, MoneyAmount.fromTreasurySell(treasury));
+            if (autoUpdateProjectMargin) {
+                double margin = exchangedRateValueUpdater.updateTreasuryExchangedValues(treasury);
+                if (margin < 0) {
+                    projectHUFBalance.addExpense(-1 * margin);
+                } else {
+                    projectHUFBalance.addIncome(margin);
+                }
             }
         }
 
         for (int i = 0; i < serviceRecordsDataModel.getSize(); i++) {
             ServiceRecord serviceRecord = serviceRecordsDataModel.getElementAt(i);
-            subtractBalance(balances, MoneyAmount.fromServiceRecord(serviceRecord));
+            MoneyAmount moneyAmount = MoneyAmount.fromServiceRecord(serviceRecord);
+            subtractBalance(balances, moneyAmount);
+            if (autoUpdateProjectMargin) {
+                projectHUFBalance.addExpense(moneyAmount.getGrossAmountHUF());
+            }
         }
 
         balances.keySet().stream().sorted().forEach(currency -> this.add(balances.get(currency)));
         BindUtils.postNotifyChange(this, "*");
+
+        if (autoUpdateProjectMargin) {
+            project.setSellingPrice(projectHUFBalance.getIncome());
+            project.setSupplyPrice(projectHUFBalance.getExpense());
+            project.setMargin(projectHUFBalance.getIncome() - projectHUFBalance.getExpense());
+            BindUtils.postNotifyChange(project, "sellingPrice", "supplyPrice", "margin");
+        }
+
     }
 
 
index 0d362d76b34740463c55aac264469ed045e71536..87f0bf5ccb5cda9bb8322064ec7bf7b220dd52b6 100644 (file)
@@ -8,8 +8,19 @@ import lombok.Setter;
 @Setter
 @Builder
 public class MoneyBalance {
+
     @Builder.Default
-    Double income = (double) 0;
+    private Double income = (double) 0;
+
     @Builder.Default
-    Double expense = (double) 0;
+    private Double expense = (double) 0;
+
+
+    public void addIncome(double income) {
+        this.income += income;
+    }
+
+    public void addExpense(double expanse) {
+        this.expense += expanse;
+    }
 }
diff --git a/sly-crm-ui/src/main/java/hu/user/lis/ui/data/common/ObjectDictionary.java b/sly-crm-ui/src/main/java/hu/user/lis/ui/data/common/ObjectDictionary.java
new file mode 100644 (file)
index 0000000..4d7a3ab
--- /dev/null
@@ -0,0 +1,6 @@
+package hu.user.lis.ui.data.common;
+
+import java.util.HashMap;
+
+public class ObjectDictionary extends HashMap<String, Object> {
+}
index fc05278ccbfa244d150f0dec3fb28a723b0c9f29..4b8a1216b547717274672eb7a8ce90eddd66a32b 100644 (file)
@@ -1,9 +1,15 @@
 package hu.user.lis.ui.editor;
 
-import hu.user.lis.db.*;
+import hu.user.lis.db.Currency;
+import hu.user.lis.db.IncomingInvoice;
+import hu.user.lis.db.Invoice;
+import hu.user.lis.db.OutgoingInvoice;
+import hu.user.lis.db.Project;
+import hu.user.lis.db.Treasury;
 import hu.user.lis.service.mnb.ExchangeRateService;
 import hu.user.lis.ui.data.MoneyBalance;
 import lombok.extern.log4j.Log4j2;
+import org.apache.http.annotation.Obsolete;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -18,6 +24,7 @@ public class ExchangedRateValueUpdater {
     @Autowired
     private ExchangeRateService exchangeRateService;
 
+    @Obsolete
     public MoneyBalance update(Project project) {
         MoneyBalance result = MoneyBalance.builder().build();
         for (IncomingInvoice incomingInvoice : project.getIncomingInvoices()) {
@@ -37,7 +44,9 @@ public class ExchangedRateValueUpdater {
         return result;
     }
 
-    private void updateTreasuryExchangedValues(Treasury treasury) {
+    public double updateTreasuryExchangedValues(Treasury treasury) {
+        double result = 0;
+
         Date initialDate = treasury.getTransactionDate();
         Currency currency = treasury.getBuyCurrency();
         if (!Currency.HUF.equals(currency) && Objects.isNull(treasury.getBuyExchangeRate())) {
@@ -59,9 +68,11 @@ public class ExchangedRateValueUpdater {
                 treasury.setSellAmountHUF(treasury.getBuyAmount() * exchangeRate);
             }
         }
+
+        return treasury.getSellAmountHUF() - treasury.getBuyAmountHUF();
     }
 
-    private double updateInvoiceExchangedValues(Invoice invoice) {
+    public double updateInvoiceExchangedValues(Invoice invoice) {
         if (!Currency.HUF.equals(invoice.getCurrency()) && Objects.isNull(invoice.getExchangeRate())) {
             Date initialDate = invoice.getPaymentDeadline();
             LocalDate exchangeDate = Objects.isNull(initialDate) ? LocalDate.now() :
index e9f97224d5c8452e81320917c5d0b2ea8f16f368..fcf816cb5ce4beb3e2aeffb8cd60f394a15bb865 100644 (file)
@@ -10,7 +10,11 @@ import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.log4j.Log4j2;
 import org.zkoss.bind.BindUtils;
-import org.zkoss.bind.annotation.*;
+import org.zkoss.bind.annotation.AfterCompose;
+import org.zkoss.bind.annotation.Command;
+import org.zkoss.bind.annotation.ContextParam;
+import org.zkoss.bind.annotation.ContextType;
+import org.zkoss.bind.annotation.Init;
 import org.zkoss.zk.ui.Component;
 import org.zkoss.zk.ui.event.Event;
 import org.zkoss.zk.ui.select.annotation.WireVariable;
@@ -70,7 +74,7 @@ public class InvoiceEditorModel extends EntityAttachmentEditorModel<Invoice> {
 
     @Override
     public void onEvent(Event evt) {
-        if (isEventForCurrentDocument(evt)) {
+        if (isPropertyChangedEventForCurrentDocument(evt)) {
 //            PropertyChangeEvent propertyEvent = (PropertyChangeEvent) evt;
 //            String[] dates = {"completionDate", "createDate", "paymentDeadline"};
 //            if (Arrays.asList(dates).contains(propertyEvent.getProperty())) {
index 8248b903d357590570a60a37adb834c047e03b31..9807d56998e32d4aae7be947a86b5fce9b781080 100644 (file)
@@ -16,7 +16,6 @@ import hu.user.lis.ui.converter.ProjectStatusConverter;
 import hu.user.lis.ui.data.AssociatesDataModel;
 import hu.user.lis.ui.data.IncomeMarginsDataModel;
 import hu.user.lis.ui.data.InvoiceDataModel;
-import hu.user.lis.ui.data.MoneyBalance;
 import hu.user.lis.ui.data.ProjectAssociatesDataModel;
 import hu.user.lis.ui.data.ProjectStatusDataModel;
 import hu.user.lis.ui.data.ProjectsDataModel;
@@ -46,6 +45,7 @@ import org.zkoss.zk.ui.util.Notification;
 import org.zkoss.zul.Panel;
 import org.zkoss.zul.Window;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -134,6 +134,12 @@ public class ProjectEditorModel extends EntityEditorModel<Project> {
         projectStatusDataModel.listActive();
     }
 
+    @Override
+    protected boolean canPropertyCauseValidation(String property) {
+        List<String> prohibitedNames = Arrays.asList("sellingPrice", "supplyPrice", "margin");
+        return !prohibitedNames.contains(property);
+    }
+
     @Override
     public void onEvent(Event evt) {
         super.onEvent(evt);
@@ -141,7 +147,6 @@ public class ProjectEditorModel extends EntityEditorModel<Project> {
             Object data = evt.getData();
             initDocuments(data);
             initDetails();
-            //validate();
         }
     }
 
@@ -149,7 +154,7 @@ public class ProjectEditorModel extends EntityEditorModel<Project> {
         initAssociates();
         serviceRecordsDataModel.search(getFormDocument(), true);
         getEntitySelectorRouter().configureSelector(Partner.class, getFormDocument(), "partner");
-        safeRecalcMargin();
+        safeRecalculateMargin();
     }
 
 
@@ -437,7 +442,10 @@ public class ProjectEditorModel extends EntityEditorModel<Project> {
 
     @Override
     protected boolean areDifferent(Project entity) {
-        return super.areDifferent(entity) || isAssociatesChanged() || !invoiceDocumentActions.isEmpty() || !treasuryDocumentActions.isEmpty();
+        return super.areDifferent(entity) ||
+                isAssociatesChanged() ||
+                !invoiceDocumentActions.isEmpty() ||
+                !treasuryDocumentActions.isEmpty();
     }
 
     @Command
@@ -449,23 +457,10 @@ public class ProjectEditorModel extends EntityEditorModel<Project> {
     @Override
     public void validate() {
         super.validate();
-        //updateExchangeRateBasedValues();
-        safeRecalcMargin();
-    }
-
-    private void updateExchangeRateBasedValues() {
-        try {
-            MoneyBalance balance = exchangedRateValueUpdater.update(getFormDocument());
-            getFormDocument().setSellingPrice(balance.getIncome());
-            getFormDocument().setSupplyPrice(balance.getExpense());
-            getFormDocument().setMargin(balance.getIncome() - balance.getExpense());
-            BindUtils.postNotifyChange(getFormDocument(), "sellingPrice", "supplyPrice", "margin");
-        } catch (Exception e) {
-            log.error(e.getMessage());
-        }
+        safeRecalculateMargin();
     }
 
-    private void safeRecalcMargin() {
+    private void safeRecalculateMargin() {
         try {
             incomeMarginsDataModel.recalculate(getFormDocument(), serviceRecordsDataModel);
         } catch (Exception e) {
index 2575d4f6ddcc08b00ca504b50eaab762ed134662..ddb40164f69e25c429877041d35c54c6e5d6b216 100644 (file)
@@ -17,7 +17,12 @@ import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.log4j.Log4j2;
 import org.zkoss.bind.PropertyChangeEvent;
-import org.zkoss.bind.annotation.*;
+import org.zkoss.bind.annotation.AfterCompose;
+import org.zkoss.bind.annotation.BindingParam;
+import org.zkoss.bind.annotation.Command;
+import org.zkoss.bind.annotation.ContextParam;
+import org.zkoss.bind.annotation.ContextType;
+import org.zkoss.bind.annotation.Init;
 import org.zkoss.zk.ui.Component;
 import org.zkoss.zk.ui.event.Event;
 import org.zkoss.zk.ui.event.Events;
@@ -27,7 +32,11 @@ import org.zkoss.zul.Window;
 
 import java.time.LocalDate;
 import java.time.ZoneId;
-import java.util.*;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 @Log4j2
@@ -119,7 +128,7 @@ public class ServiceRecordEditorModel extends EntityAttachmentEditorModel<Servic
 
     @Override
     public void onEvent(Event evt) {
-        if (isEventForCurrentDocument(evt)) {
+        if (isPropertyChangedEventForCurrentDocument(evt)) {
             PropertyChangeEvent propertyEvent = (PropertyChangeEvent) evt;
             log.info("Property changed: {}", propertyEvent.getProperty());
 
index ced6f81723fb04b546c7e9ce40b88dbd2fd0d100..e036dee2c0e66f5560fdfa9f1dc095e1426108c1 100644 (file)
@@ -125,15 +125,20 @@ public abstract class EntityEditorModel<T extends Serializable> extends Abstract
 
     @Override
     public void onEvent(Event evt) {
-        if (isEventForCurrentDocument(evt)) {
+        if (isPropertyChangedEventForCurrentDocument(evt)) {
             validate();
         }
     }
 
-    protected boolean isEventForCurrentDocument(Event evt) {
+    protected boolean canPropertyCauseValidation(String property) {
+        return true;
+    }
+
+    protected boolean isPropertyChangedEventForCurrentDocument(Event evt) {
         if (evt instanceof PropertyChangeEvent) {
             PropertyChangeEvent propertyEvent = (PropertyChangeEvent) evt;
-            return (Objects.nonNull(propertyEvent.getBase()) && Objects.nonNull(formDocument) && propertyEvent.getBase().equals(formDocument));
+            return canPropertyCauseValidation(propertyEvent.getProperty()) &&
+                    (Objects.nonNull(propertyEvent.getBase()) && Objects.nonNull(formDocument) && propertyEvent.getBase().equals(formDocument));
         }
         return false;
     }
diff --git a/sly-crm-ui/src/main/java/hu/user/lis/ui/properties/GeneralSettings.java b/sly-crm-ui/src/main/java/hu/user/lis/ui/properties/GeneralSettings.java
new file mode 100644 (file)
index 0000000..225f39a
--- /dev/null
@@ -0,0 +1,16 @@
+package hu.user.lis.ui.properties;
+
+import hu.user.lis.ui.auth.CurrentProfile;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class GeneralSettings {
+    @Autowired
+    private CurrentProfile currentProfile;
+
+    public boolean getAutoProjectMarginUpdate() {
+        return (boolean) currentProfile.getGeneralSettings().get("autoProjectMarginUpdate");
+    }
+
+}
index 882fe3d7062dc05560d69a27420395a9a9c0eb87..264cdb2db361805810c077d801825f8182d368f4 100644 (file)
@@ -1,6 +1,7 @@
 package hu.user.lis.ui.view;
 
 import hu.user.lis.db.ProjectStatus;
+import hu.user.lis.ui.auth.CurrentProfile;
 import hu.user.lis.ui.data.ProjectStatusDataModel;
 import hu.user.lis.ui.view.common.AsyncBaseModel;
 import lombok.Getter;
@@ -19,13 +20,18 @@ import org.zkoss.zul.Panel;
 @Log4j2
 @VariableResolver(DelegatingVariableResolver.class)
 public class SettingsViewModel extends AsyncBaseModel {
-    @WireVariable
+
     @Getter
+    @WireVariable
     ProjectStatusDataModel projectStatusDataModel;
 
     @Getter
     private ProjectStatus selectedProjectStatus;
 
+    @Getter
+    @WireVariable
+    private CurrentProfile currentProfile;
+
     @Init
     public void init() {
         Clients.evalJavaScript("pushNav('/settings')");
@@ -74,11 +80,16 @@ public class SettingsViewModel extends AsyncBaseModel {
             projectStatusDataModel.save(entity);
             projectStatusDataModel.listAll();
         } catch (Exception e) {
-            Messagebox.show("A mentés sikertelen! A elvárt az egyedi státusz elnevezés.", "Hiba", Messagebox.OK , Messagebox.ERROR);
+            Messagebox.show("A mentés sikertelen! A elvárt az egyedi státusz elnevezés.", "Hiba", Messagebox.OK, Messagebox.ERROR);
         }
     }
 
-    public void setSelectedProjectStatus(ProjectStatus selectedProjectStatus) {
-        this.selectedProjectStatus = selectedProjectStatus;
+    @Command
+    public void onGeneralSettingsChanged() {
+        try {
+            currentProfile.saveGeneralSettings();
+        } catch (Exception e) {
+            Messagebox.show("A mentés sikertelen!", "Hiba", Messagebox.OK, Messagebox.ERROR);
+        }
     }
 }
index 8e0b61d8739654b0d54c16dbbe0ff15c19e60a66..eebc95351c4abb7921b2e97f170ee65112d2120d 100644 (file)
                                         <vlayout vflex="true" style="overflow: auto">
                                             <hbox pack="stretch">
                                                 <div class="input-group">
-                                                    <span class="input-group-addon">Bevétel:</span>
+                                                    <span class="input-group-addon">Kiadás:</span>
                                                     <label value="@load(vm.formDocument.supplyPrice) @converter('hu.user.lis.ui.converter.DoubleToStringConverter')"/>
                                                     <span class="input-group-addon">HUF</span>
                                                 </div>
                                                 <div class="input-group">
-                                                    <span class="input-group-addon">Kiadás:</span>
+                                                    <span class="input-group-addon">Bevétel:</span>
                                                     <label value="@load(vm.formDocument.sellingPrice) @converter('hu.user.lis.ui.converter.DoubleToStringConverter')"/>
                                                     <span class="input-group-addon">HUF</span>
                                                 </div>
index f0fe4290e4456b016a7980d3f54d4755a3319675..2145396398a4813ce7488b5a4d0f5c4d6cb908e0 100644 (file)
@@ -6,7 +6,20 @@
             </hbox>
         </caption>
 
-        <panel collapsible="true" open="false" border="rounded"
+        <panel collapsible="true" open="true" border="rounded"
+               onOpen="@command('onOpenFormPanel', parentPanel=centerPanel)">
+            <caption label="Általános működés" style="cursor: pointer"
+                     onClick="@command('onClickFormPanel', parentPanel=centerPanel, panel=self.parent)"/>
+            <panelchildren>
+                <vlayout>
+                    <checkbox label="Projekt árrés automatikus frissítése" mold="switch"
+                              checked="@bind(vm.currentProfile.generalSettings['autoProjectMarginUpdate'])"
+                              onCheck="@command('onGeneralSettingsChanged')"/>
+                </vlayout>
+            </panelchildren>
+        </panel>
+        <separator orient="vertical"/>
+        <panel collapsible="true" open="true" border="rounded"
                onOpen="@command('onOpenFormPanel', parentPanel=centerPanel)">
             <caption label="Projekt státusz" style="cursor: pointer"
                      onClick="@command('onClickFormPanel', parentPanel=centerPanel, panel=self.parent)"/>