xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>sly-crm-app</artifactId>
- <version>0.2.3</version>
+ <version>0.2.4</version>
<name>SLY-CRM</name>
<parent>
<groupId>hu.user</groupId>
import-invoice:
input-path: /temp/invoice-import
project-id-pattern: \d{4}-\d{4}
+ old-project-id-pattern: \d{8}
service:
nav:
trust:
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
-import javax.persistence.CascadeType;
import javax.persistence.Entity;
-import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
-import javax.persistence.OneToMany;
-import javax.persistence.OrderColumn;
import javax.persistence.Transient;
import java.util.List;
@Transient
private List<OutgoingInvoice> outgoingInvoices;
- @OneToMany(targetEntity = Treasury.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
- @JoinColumn(name = "project_id", referencedColumnName = "id")
- @Fetch(FetchMode.JOIN)
- @OrderColumn(name = "pos")
- @JsonIncludeProperties({"id"})
+ // @OneToMany(targetEntity = Treasury.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
+// @JoinColumn(name = "project_id", referencedColumnName = "id")
+// @Fetch(FetchMode.JOIN)
+// @OrderColumn(name = "pos")
+// @JsonIncludeProperties({"id"})
+ @Transient
private List<Treasury> treasuries;
private String name;
package hu.user.lis.db.repository;
+import hu.user.lis.db.Project;
import hu.user.lis.db.Treasury;
import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.List;
+
public interface TreasuryRepository extends JpaRepository<Treasury, Long> {
+ List<Treasury> getAllByProjectOrderByTransactionDate(Project project);
+
+ List<Treasury> getAllByProjectIsNull();
+
}
package hu.user.lis.ui.data;
-import hu.user.lis.db.IncomingInvoice;
-import hu.user.lis.db.Invoice;
-import hu.user.lis.db.InvoiceStatus;
-import hu.user.lis.db.OutgoingInvoice;
+import hu.user.lis.db.*;
import hu.user.lis.db.repository.InvoiceRepository;
import hu.user.lis.service.data.EntityDataService;
import hu.user.lis.service.data.EntityDocumentAction;
public void delete(Invoice selectedIncomingInvoice) {
invoiceRepository.delete(selectedIncomingInvoice);
}
+
+ public void fillProjectIncomingInvoices(Project project) {
+ List<IncomingInvoice> incoming = invoiceRepository.findAllByProjectAndIncomingIsTrue(project);
+ project.setIncomingInvoices(incoming);
+ }
+
+ public void fillProjectOutgoingInvoices(Project project) {
+ List<OutgoingInvoice> outgoing = invoiceRepository.findAllByProjectAndIncomingIsFalse(project);
+ project.setOutgoingInvoices(outgoing);
+ }
}
--- /dev/null
+package hu.user.lis.ui.data;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Builder
+public class MoneyBalance {
+ @Builder.Default
+ Double income = (double) 0;
+ @Builder.Default
+ Double expense = (double) 0;
+}
import hu.user.lis.db.Partner;
import hu.user.lis.db.Project;
import hu.user.lis.db.ProjectStatus;
+import hu.user.lis.db.Treasury;
import hu.user.lis.db.repository.InvoiceRepository;
import hu.user.lis.db.repository.ProjectRepository;
+import hu.user.lis.db.repository.TreasuryRepository;
import hu.user.lis.db.repository.filter.ProjectFilter;
import hu.user.lis.service.data.EntityDataService;
import hu.user.lis.ui.data.common.CachedSpringDataModel;
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ProjectsDataModel extends CachedSpringDataModel<Project> {
@Autowired
- ProjectRepository projectRepository;
+ private ProjectRepository projectRepository;
@Autowired
- InvoiceRepository invoiceRepository;
+ private InvoiceRepository invoiceRepository;
@Autowired
- ProjectStatusDataModel projectStatusDataModel;
+ private TreasuryRepository treasuryRepository;
@Autowired
- EntityDataService<Project> projectDataService;
+ private ProjectStatusDataModel projectStatusDataModel;
+
+ @Autowired
+ private EntityDataService<Project> projectDataService;
private ProjectFilter filter;
selectedEntity.getOutgoingInvoices().forEach(i -> i.setProject(selectedEntity));
invoiceRepository.saveAll(selectedEntity.getIncomingInvoices());
invoiceRepository.saveAll(selectedEntity.getOutgoingInvoices());
+ treasuryRepository.saveAll(selectedEntity.getTreasuries());
result.setIncomingInvoices(selectedEntity.getIncomingInvoices());
result.setOutgoingInvoices(selectedEntity.getOutgoingInvoices());
+ result.setTreasuries(selectedEntity.getTreasuries());
+
+ List<Treasury> orphanTreasuries = treasuryRepository.getAllByProjectIsNull();
+ treasuryRepository.deleteAll(orphanTreasuries);
return result;
}
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.FieldComparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
+import java.util.*;
@Component
@Log4j2
@Override
public List<ServiceRecord> getResultSet(int page, int pageSize, FieldComparator sortComparator) {
if (groupByAssociate) {
- return getGroupedServiceRecords().values().stream().collect(Collectors.toList());
+ return new ArrayList<>(getGroupedServiceRecords().values());
}
Pageable pageable = createPageable(page, pageSize, sortComparator);
- List<ServiceRecord> result = listAll ? serviceRecordRepository.findAll(pageable).toList() :
+ return listAll ? serviceRecordRepository.findAll(pageable).toList() :
serviceRecordRepository.search(filterProjectId, filterAssociateId, pageable);
- return result;
}
Map<Long, ServiceRecord> getGroupedServiceRecords() {
Map<Long, ServiceRecord> grouped = new HashMap<>();
List<ServiceRecord> allEntities = serviceRecordRepository.findByProjectId(filterProjectId);
- allEntities.stream().forEach(s -> {
- double cost = s.getWorkHours() * s.getAssociate().getMonthlyCost() / 160;
+ allEntities.forEach(s -> {
if (grouped.containsKey(s.getAssociate().getId())) {
ServiceRecord serviceRecord = grouped.get(s.getAssociate().getId());
- serviceRecord.setCost(serviceRecord.getCost() + cost);
+ serviceRecord.setCost(serviceRecord.getCost() + s.getCost());
serviceRecord.setWorkHours(serviceRecord.getWorkHours() + s.getWorkHours());
} else {
ServiceRecord serviceRecord = serviceRecordDataService.clone(s);
serviceRecord.setWorkDay(null);
serviceRecord.setId(0L);
- serviceRecord.setCost(cost);
grouped.put(serviceRecord.getAssociate().getId(), serviceRecord);
}
});
package hu.user.lis.ui.data;
+import hu.user.lis.db.Project;
import hu.user.lis.db.Treasury;
+import hu.user.lis.db.repository.TreasuryRepository;
import hu.user.lis.service.data.EntityDataService;
import hu.user.lis.service.data.TreasuryService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
+import java.util.List;
+
@Component
@Log4j2
public class TreasuryDataModel {
+ @Autowired
+ private TreasuryRepository treasuryRepository;
@Autowired
- EntityDataService<Treasury> treasuryDataService;
+ private EntityDataService<Treasury> treasuryDataService;
@Autowired
- TreasuryService treasuryService;
+ private TreasuryService treasuryService;
public Treasury createNew() {
return treasuryService.createNew();
return treasuryDataService.clone(entity);
}
+ public void fillProjectTreasuries(Project project) {
+ List<Treasury> treasury = treasuryRepository.getAllByProjectOrderByTransactionDate(project);
+ project.setTreasuries(treasury);
+ }
}
--- /dev/null
+package hu.user.lis.ui.editor;
+
+import hu.user.lis.db.*;
+import hu.user.lis.service.mnb.ExchangeRateService;
+import hu.user.lis.ui.data.MoneyBalance;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Objects;
+
+@Log4j2
+@Service
+public class ExchangedRateValueUpdater {
+ @Autowired
+ private ExchangeRateService exchangeRateService;
+
+ public MoneyBalance update(Project project) {
+ MoneyBalance result = MoneyBalance.builder().build();
+ for (IncomingInvoice incomingInvoice : project.getIncomingInvoices()) {
+ double income = updateInvoiceExchangedValues(incomingInvoice);
+ result.setIncome(result.getIncome() + income);
+ }
+
+ for (OutgoingInvoice outgoingInvoice : project.getOutgoingInvoices()) {
+ double expense = updateInvoiceExchangedValues(outgoingInvoice);
+ result.setExpense(result.getExpense() + expense);
+ }
+
+ for (Treasury treasury : project.getTreasuries()) {
+ updateTreasuryExchangedValues(treasury);
+ }
+ log.info("Exchanged rates and values updated");
+ return result;
+ }
+
+ private void updateTreasuryExchangedValues(Treasury treasury) {
+ Date initialDate = treasury.getTransactionDate();
+ Currency currency = treasury.getBuyCurrency();
+ if (!Currency.HUF.equals(currency) && Objects.isNull(treasury.getBuyExchangeRate())) {
+ LocalDate exchangeDate = Objects.isNull(initialDate) ? LocalDate.now() :
+ initialDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+ Double exchangeRate = exchangeRateService.getExchangeRate(currency, exchangeDate);
+ if (Objects.nonNull(exchangeRate)) {
+ treasury.setBuyExchangeRate(exchangeRate);
+ treasury.setBuyAmountHUF(treasury.getBuyAmount() * exchangeRate);
+ }
+ }
+ currency = treasury.getSellCurrency();
+ if (!Currency.HUF.equals(currency) && Objects.isNull(treasury.getSellExchangeRate())) {
+ LocalDate exchangeDate = Objects.isNull(initialDate) ? LocalDate.now() :
+ initialDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+ Double exchangeRate = exchangeRateService.getExchangeRate(currency, exchangeDate);
+ if (Objects.nonNull(exchangeRate)) {
+ treasury.setSellExchangeRate(exchangeRate);
+ treasury.setSellAmountHUF(treasury.getBuyAmount() * exchangeRate);
+ }
+ }
+ }
+
+ private 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() :
+ initialDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+ Double exchangeRate = exchangeRateService.getExchangeRate(invoice.getCurrency(), exchangeDate);
+ if (Objects.nonNull(exchangeRate)) {
+ invoice.setExchangeRate(exchangeRate);
+ invoice.setNetAmountHUF(invoice.getNetAmount() * exchangeRate);
+ invoice.setGrossAmountHUF(invoice.getGrossAmount() * exchangeRate);
+ }
+ }
+ if (Currency.HUF.equals(invoice.getCurrency())) {
+ invoice.setExchangeRate((double) 1);
+ invoice.setNetAmountHUF(invoice.getNetAmount());
+ invoice.setGrossAmountHUF(invoice.getGrossAmount());
+ }
+
+ return Objects.isNull(invoice.getGrossAmountHUF()) ? 0 : invoice.getGrossAmountHUF();
+ }
+
+
+}
package hu.user.lis.ui.editor;
import com.google.common.collect.ImmutableMap;
-import hu.user.lis.db.Associate;
-import hu.user.lis.db.IncomingInvoice;
-import hu.user.lis.db.Invoice;
-import hu.user.lis.db.OutgoingInvoice;
-import hu.user.lis.db.Partner;
-import hu.user.lis.db.Project;
-import hu.user.lis.db.ProjectAssociate;
-import hu.user.lis.db.Treasury;
-import hu.user.lis.db.repository.InvoiceRepository;
+import hu.user.lis.db.*;
import hu.user.lis.service.data.EntityDocumentAction;
import hu.user.lis.service.data.EntityDocumentService;
import hu.user.lis.ui.Constants;
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.ProjectAssociatesDataModel;
-import hu.user.lis.ui.data.ProjectStatusDataModel;
-import hu.user.lis.ui.data.ProjectsDataModel;
-import hu.user.lis.ui.data.ServiceRecordsDataModel;
-import hu.user.lis.ui.data.TreasuryDataModel;
+import hu.user.lis.ui.data.*;
import hu.user.lis.ui.editor.common.Editors;
import hu.user.lis.ui.editor.common.EntityEditorModel;
import hu.user.lis.ui.editor.validator.FormValidator;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.transaction.annotation.Transactional;
import org.zkoss.bind.BindUtils;
-import org.zkoss.bind.annotation.BindingParam;
-import org.zkoss.bind.annotation.Command;
-import org.zkoss.bind.annotation.Destroy;
-import org.zkoss.bind.annotation.Init;
-import org.zkoss.bind.annotation.NotifyChange;
+import org.zkoss.bind.annotation.*;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.select.annotation.WireVariable;
ProjectsDataModel projectsDataModel;
@WireVariable
private InvoiceDataModel invoiceDataModel;
- @WireVariable
- private InvoiceRepository invoiceRepository;
+ // @WireVariable
+// private InvoiceRepository invoiceRepository;
@WireVariable
private TreasuryDataModel treasuryDataModel;
@Getter
@WireVariable
private EntityDocumentService entityDocumentService;
+ @WireVariable
+ private ExchangedRateValueUpdater exchangedRateValueUpdater;
+
@Override
public FormValidator<Project> getFormValidator() {
return projectFormValidator;
Object data = evt.getData();
initDocuments(data);
initDetails();
+ //validate();
}
}
Clients.evalJavaScript(String.format("pushNav('/project/%d')", id));
log.info("Loading entity {} to editor", id);
Project project = projectsDataModel.getById(id);
- List<IncomingInvoice> incoming = invoiceRepository.findAllByProjectAndIncomingIsTrue(project);
- List<OutgoingInvoice> outgoing = invoiceRepository.findAllByProjectAndIncomingIsFalse(project);
- project.setIncomingInvoices(incoming);
- project.setOutgoingInvoices(outgoing);
+ invoiceDataModel.fillProjectIncomingInvoices(project);
+ invoiceDataModel.fillProjectOutgoingInvoices(project);
+ treasuryDataModel.fillProjectTreasuries(project);
+
setFormDocument(project);
setOrigDocument(projectsDataModel.clone(getFormDocument()));
}
@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());
+ }
+ }
+
private void safeRecalcMargin() {
try {
incomeMarginsDataModel.recalculate(getFormDocument(), serviceRecordsDataModel);
if (isSaveEnabled()) {
Optional<ProjectAssociate> opProjectAssociate = projectAssociatesDataModel.searchByAssociateAndProject(getFormDocument().getAssociate().getId(),
getFormDocument().getProject().getId());
-
-
List<EntityDocumentAction> actions = getEntityDocumentDataModel().getActions();
if (opProjectAssociate.isPresent()) {
Events.postEvent(new SaveEntityWithAttachmentEvent<>(getFormDocument(), actions, target));
import org.zkoss.zk.ui.select.annotation.WireVariable;
import org.zkoss.zkplus.spring.DelegatingVariableResolver;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
@Log4j2
Editors.doEdit(Editors.SERVICE_RECORD, entity, getSelectedEntity(), this::saveServiceRecord);
}
+ public void calculateCost(ServiceRecord entity) {
+ double cost = 0;
+ int monthWorkDays = 5;
+ int workHoursPerDay = 8;
+ int weeksInMonth = 4;
+ try {
+ Optional<Double> opMonthlyCost = Optional.ofNullable(entity.getAssociate()).map(Associate::getMonthlyCost);
+ if (opMonthlyCost.isPresent() && entity.getWorkHours() > 0) {
+ cost = (opMonthlyCost.get() * entity.getWorkHours()) / (monthWorkDays * workHoursPerDay * weeksInMonth);
+ entity.setCost(cost);
+ }
+ } catch (Exception e) {
+ log.catching(e);
+ }
+ }
+
public void saveServiceRecord(SaveEntityEvent<ServiceRecord> event) {
if (event.isSave()) {
ServiceRecord modifiedEntity = event.getData();
List<EntityDocumentAction> actions = event.withAttachment().getActions();
modifiedEntity.setSigned(!actions.isEmpty());
+ calculateCost(modifiedEntity);
serviceRecordsDataModel.save(modifiedEntity);
entityDocumentService.executeActions(modifiedEntity.getId(), actions);
refresh();
</tabs>
<tabpanels>
<tabpanel>
- <vlayout vflex="true">
- <window title="Árrés (HUF)">
- <hbox>
- <panel title="Bevétel">
- <panelchildren>
- <label value="@load(vm.formDocument.supplyPrice)"/>
- </panelchildren>
- </panel>
- <panel title="Kiadás">
- <panelchildren>
- <label value="@load(vm.formDocument.sellingPrice)"/>
- </panelchildren>
- </panel>
- <panel title="Árrés">
- <panelchildren>
- <label value="@load(vm.formDocument.margin)"/>
- </panelchildren>
- </panel>
- </hbox>
- </window>
+ <vlayout vflex="true" style="overflow: auto">
+ <hbox pack="stretch">
+ <div class="input-group">
+ <span class="input-group-addon">Bevétel:</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>
+ <label value="@load(vm.formDocument.sellingPrice) @converter('hu.user.lis.ui.converter.DoubleToStringConverter')"/>
+ <span class="input-group-addon">HUF</span>
+ </div>
+ <div class="input-group">
+ <span class="input-group-addon">Különbözet:</span>
+ <label value="@load(vm.formDocument.margin) @converter('hu.user.lis.ui.converter.DoubleToStringConverter')"/>
+ <span class="input-group-addon">HUF</span>
+ </div>
+ </hbox>
<grid sclass="no-hover-grid" oddRowSclass="none"
model="@load(vm.incomeMarginsDataModel)"
forward="onOK=submit.onClick, onCancel=cancel.onClick"
emptyMessage="Nem kalkulálható!">
- <columns visible="false">
+ <columns visible="true">
<column width="50px" label="Pénznem"/>
<column width="100%" label="Nettó összeg"/>
<column width="100%" label="Bruttó összeg"/>
<tabpanels>
<tabpanel>
<vlayout hflex="true">
- <label value="Partner"/>
- <entity-selector selector_id="invoice_partner" entity="Partner"/>
+ <!-- <label value="Partner"/>-->
+ <!-- <entity-selector selector_id="invoice_partner" entity="Partner"/>-->
<!-- <vlayout hflex="true">-->
<!-- <label value="Projekt"/>-->
<!-- <hlayout>-->
<toolbar align="end">
<toolbarbutton label="Szerkeszt" iconSclass="z-icon-edit" onClick="@command('onEdit')"
disabled="@load(empty vm.selectedEntity)"/>
- <!-- <toolbarbutton label="Projekt módosítás" iconSclass="z-icon-forward"-->
- <!-- onClick="@command('onChangeProject')" disabled="@load(empty vm.selectedEntity)"/>-->
+ <toolbarbutton label="Projekt módosítás" iconSclass="z-icon-forward"
+ onClick="@command('onChangeProject')" disabled="@load(empty vm.selectedEntity)"/>
<toolbarbutton label="Kiegyenlít" iconSclass="z-icon-credit-card"
onClick="@command('onInvoicePayment')"
disabled="@load(empty vm.selectedEntity || vm.selectedEntity.planned)"/>
+.badge {
+ display: inline-block;
+ min-width: 10px;
+ padding: 3px 7px;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ background-color: #999;
+ border-radius: 10px;
+}
+
+.input-group {
+ margin: 10px;
+}
+
+.input-group > .z-label {
+ font-size: 1.2em;
+ font-weight: bold;
+}
+
.stretch-content > .z-caption-content {
width: 100%
}
opacity: 1;
}
+/*
.no-hover-grid .z-row .z-grid-odd,
.no-hover-grid .z-row:hover>.z-row-inner,
.no-hover-grid .z-row:hover>.z-cell {
- background: none;
+ background: transparent;
+ text-color: black;
}
-/**/
+*/
textarea {
resize: none;
}
]]></zscript>
<button label="copy client-side" w:onClick="copyToClipboard('Client Side', 'value-test123')"/>
+ <label class="badge">42</label>
+
</zk>
\ No newline at end of file