This commit is contained in:
Ralph Soika 2025-03-31 20:42:56 +02:00
parent 02f24df8a3
commit 93263280dc
5 changed files with 5 additions and 576 deletions

View file

@ -1,206 +0,0 @@
package org.imixs.workflow.office.journal;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.imixs.workflow.ItemCollection;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
@Entity
@Table(name = "journal_accounting")
public class JournalAccounting {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(precision = 19, scale = 4, nullable = false)
private BigDecimal amount;
// base amount (optional)
@Column(precision = 19, scale = 4)
private BigDecimal baseAmount;
// ISO currency code (e.g. "USD", "GBP")
@Column(length = 3)
private String currency;
// exchange rate (foreign currency -> base currency)
@Column(precision = 19, scale = 6)
private BigDecimal exchangeRate;
@Column()
private String reference;
@Column()
private String workflowGroup;
@Column()
private String modelVersion;
@Column()
private int taskId;
@Temporal(TemporalType.DATE)
@Column(nullable = false)
private Date date;
@Column(nullable = false)
private String type;
@OneToMany(mappedBy = "journalAccounting", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<JournalAccountingAttribute> attributes = new HashSet<>();
// Constructors
public JournalAccounting() {
}
public JournalAccounting(ItemCollection workitem) {
updateWorkitem(workitem);
}
public void updateWorkitem(ItemCollection workitem) {
this.reference = workitem.getUniqueID();
this.workflowGroup = workitem.getWorkflowGroup();
this.modelVersion = workitem.getModelVersion();
this.taskId = workitem.getTaskID();
this.type = workitem.getType();
}
// Helper methods for attribute management
public void addAttribute(String key, String value) {
JournalAccountingAttribute attr = new JournalAccountingAttribute(this, key, value);
attributes.add(attr);
}
public String getAttribute(String key) {
return attributes.stream()
.filter(a -> a.getKey().equals(key))
.map(JournalAccountingAttribute::getValue)
.findFirst()
.orElse(null);
}
public void removeAttribute(String key) {
attributes.removeIf(attr -> attr.getKey().equals(key));
}
public Set<JournalAccountingAttribute> getAttributesByKey(String key) {
Set<JournalAccountingAttribute> result = new HashSet<>();
for (JournalAccountingAttribute attr : attributes) {
if (attr.getKey().equals(key)) {
result.add(attr);
}
}
return result;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public BigDecimal getBaseAmount() {
return baseAmount;
}
public void setBaseAmount(BigDecimal baseAmount) {
this.baseAmount = baseAmount;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public BigDecimal getExchangeRate() {
return exchangeRate;
}
public void setExchangeRate(BigDecimal exchangeRate) {
this.exchangeRate = exchangeRate;
}
public String getReference() {
return reference;
}
public void setReference(String businessProcessReference) {
this.reference = businessProcessReference;
}
public String getWorkflowGroup() {
return workflowGroup;
}
public void setWorkflowGroup(String workflowGroup) {
this.workflowGroup = workflowGroup;
}
public String getModelVersion() {
return modelVersion;
}
public void setModelVersion(String modelVersion) {
this.modelVersion = modelVersion;
}
public int getTaskId() {
return taskId;
}
public void setTaskId(int taskId) {
this.taskId = taskId;
}
public Date getDate() {
return date;
}
public void setDate(Date entryDate) {
this.date = entryDate;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Set<JournalAccountingAttribute> getAttributes() {
return attributes;
}
public void setAttributes(Set<JournalAccountingAttribute> attributes) {
this.attributes = attributes;
}
}

View file

@ -1,95 +0,0 @@
package org.imixs.workflow.office.journal;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Entity
@Table(name = "journal_accounting_attribute")
public class JournalAccountingAttribute {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(optional = false)
private JournalAccounting journalAccounting;
@Column(nullable = false)
private String key;
@Column(nullable = false)
private String value;
// Constructors
public JournalAccountingAttribute() {
}
public JournalAccountingAttribute(JournalAccounting journalAccounting, String key, String value) {
this.journalAccounting = journalAccounting;
this.key = key;
this.value = value;
}
// Standard getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public JournalAccounting getJournalAccounting() {
return journalAccounting;
}
public void setJournalAccounting(JournalAccounting journalAccounting) {
this.journalAccounting = journalAccounting;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
// Equals and hashCode for proper collection behavior
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
JournalAccountingAttribute that = (JournalAccountingAttribute) o;
if (id != null ? !id.equals(that.id) : that.id != null)
return false;
if (!key.equals(that.key))
return false;
return value.equals(that.value);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + key.hashCode();
result = 31 * result + value.hashCode();
return result;
}
}

View file

@ -1,270 +0,0 @@
/*******************************************************************************
* Imixs Workflow
* Copyright (C) 2001, 2011 Imixs Software Solutions GmbH,
* http://www.imixs.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You can receive a copy of the GNU General Public
* License at http://www.gnu.org/licenses/gpl.html
*
* Project:
* http://www.imixs.org
* http://java.net/projects/imixs-workflow
*
* Contributors:
* Imixs Software Solutions GmbH - initial API and implementation
* Ralph Soika - Software Developer
*******************************************************************************/
package org.imixs.workflow.office.journal;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.DocumentService;
import org.imixs.workflow.exceptions.AccessDeniedException;
import org.imixs.workflow.faces.data.WorkflowEvent;
import com.alexanderlogistics.InvoiceUtil;
import jakarta.annotation.security.DeclareRoles;
import jakarta.annotation.security.RolesAllowed;
import jakarta.annotation.security.RunAs;
import jakarta.ejb.Stateless;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
/**
* Der BusinessPartnerService stellt Methoden für den Zugriff auf Business
* Partner bereit.
*
* @author rsoika
*
*/
@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
"org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
"org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
@Stateless
@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
public class JournalAccountingService {
@PersistenceContext(unitName = "org.imixs.workflow.jpa")
private EntityManager entityManager;
@Inject
DocumentService documentService;
/**
* WorkflowEvent listener to store an accounting entry
*
* Demo:
* invoice:
* http://localhost:8080/pages/workitems/workitem.xhtml?id=a6524e28-eb06-4a0d-ae2f-f0cac29b126f
* payment:
* http://localhost:8080/pages/workitems/workitem.xhtml?id=e2aa082f-7e61-4cbe-abc8-be06d1aacc3f
*
* @param workflowEvent
* @throws AccessDeniedException
*/
public void onWorkflowEvent(@Observes WorkflowEvent workflowEvent) throws AccessDeniedException {
ItemCollection workitem = workflowEvent.getWorkitem();
if (workitem == null) {
return;
}
if (WorkflowEvent.WORKITEM_AFTER_PROCESS == workflowEvent.getEventType()) {
// create / update?
if (workitem.getModelVersion().startsWith("rechnungsausgang-")) {
processInvoice(workitem);
}
// Payment?
if (workitem.getModelVersion().startsWith("zahlungseingang-")) {
processPayment(workitem);
}
}
}
/**
* Process an invoice workitem - creates or updates accounting entry
*
* The reference is the uniqueId of the invoice.
*/
private void processInvoice(ItemCollection workitem) {
// Try to find existing entry
JournalAccounting accounting = findAccountingEntryByInvoice(workitem.getUniqueID());
// Create new or update existing
if (accounting == null) {
accounting = new JournalAccounting(workitem);
} else {
accounting.updateWorkitem(workitem);
}
// Update/set values
accounting.setDate(workitem.getItemValueDate("invoice.date"));
BigDecimal amount = BigDecimal.valueOf(workitem.getItemValueDouble("invoice.total"))
.setScale(2, RoundingMode.HALF_UP);
accounting.setAmount(amount);
accounting.setCurrency(workitem.getItemValueString("invoice.currency"));
// foreign currency
if (workitem.getItemValueDouble("invoice.rate") != 0.0) {
// YES
BigDecimal exchangeRate = BigDecimal.valueOf(workitem.getItemValueDouble("invoice.rate"))
.setScale(6, RoundingMode.HALF_UP);
// Hauswährung = Fremdwährung * Wechselkurs (auf 2 Stellen gerundet)
BigDecimal baseAmount = amount.divide(exchangeRate, 6, RoundingMode.HALF_UP).setScale(2,
RoundingMode.HALF_UP);
accounting.setExchangeRate(exchangeRate);
accounting.setBaseAmount(baseAmount);
} else {
// NO
accounting.setExchangeRate(null);
accounting.setBaseAmount(null);
}
// Update attributes
// Clear and add attributes (or use selective update if needed)
accounting.getAttributes().clear();
accounting.addAttribute("dbtr.name", workitem.getItemValueString("dbtr.name"));
accounting.addAttribute("dbtr.number", workitem.getItemValueString("dbtr.number"));
accounting.addAttribute("space.name", workitem.getItemValueString("space.name"));
accounting.addAttribute("country", workitem.getItemValueString("invoice.country"));
// Persist or merge
if (accounting.getId() == null) {
entityManager.persist(accounting);
} else {
entityManager.merge(accounting);
}
}
/**
* Process a payment workitem - creates entries and links to invoices
*/
private void processPayment(ItemCollection workitem) {
List<ItemCollection> paymentDetails = InvoiceUtil.explodeChildList(workitem, "payment.details");
for (ItemCollection payment : paymentDetails) {
// load the invoice document
String invoiceRef = payment.getItemValueString("$uniqueid");
ItemCollection invoice = documentService.load(invoiceRef);
BigDecimal amount = BigDecimal.valueOf(-payment.getItemValueDouble("payment.amount"))
.setScale(2, RoundingMode.HALF_UP);
// Find existing payment entry or create new one
JournalAccounting accounting = findAccountingEntryByPaymentReference(invoice.getUniqueID(),
workitem.getUniqueID());
if (accounting == null) {
accounting = new JournalAccounting(workitem);
} else {
// update
accounting.updateWorkitem(workitem);
}
// set reference to invoice
accounting.setReference(invoice.getUniqueID());
// Update payment values
accounting.setDate(workitem.getItemValueDate("payment.date"));
accounting.setCurrency(invoice.getItemValueString("invoice.currency"));
accounting.setAmount(amount);
// foreign currency
if (invoice.getItemValueDouble("invoice.rate") != 0.0) {
// YES
BigDecimal exchangeRate = BigDecimal.valueOf(invoice.getItemValueDouble("invoice.rate"))
.setScale(6, RoundingMode.HALF_UP);
// Hauswährung = Fremdwährung * Wechselkurs (auf 2 Stellen gerundet)
BigDecimal baseAmount = amount.divide(exchangeRate, 6, RoundingMode.HALF_UP).setScale(2,
RoundingMode.HALF_UP);
accounting.setExchangeRate(exchangeRate);
accounting.setBaseAmount(baseAmount);
} else {
// NO
accounting.setExchangeRate(null);
accounting.setBaseAmount(null);
}
// Update attributes
// Clear and add attributes (or use selective update if needed)
accounting.getAttributes().clear();
accounting.addAttribute("payment.reference", workitem.getUniqueID());
accounting.addAttribute("dbtr.name", workitem.getItemValueString("dbtr.name"));
accounting.addAttribute("dbtr.number", workitem.getItemValueString("dbtr.number"));
accounting.addAttribute("space.name", workitem.getItemValueString("space.name"));
// Persist or merge
if (accounting.getId() == null) {
entityManager.persist(accounting);
} else {
entityManager.merge(accounting);
}
}
}
/**
* Find an accounting entry by its business process reference
*/
private JournalAccounting findAccountingEntryByInvoice(String uniqueId) {
try {
return entityManager.createQuery(
"SELECT a FROM JournalAccounting a " +
"WHERE a.reference = :ref ",
JournalAccounting.class)
.setParameter("ref", uniqueId)
.getSingleResult();
} catch (Exception e) {
// No result found
return null;
}
}
/**
* Find an invoice accounting entry pointing to a given payment reference
*
* @param invoiceReference
* @param paymentReference
* @return
*/
public JournalAccounting findAccountingEntryByPaymentReference(String invoiceReference, String paymentReference) {
try {
String jpql = "SELECT account FROM JournalAccounting account " +
"JOIN account.attributes atr " +
"WHERE account.reference = :reference " +
"AND atr.key = 'payment.reference' AND atr.value = :paymentReference";
TypedQuery<JournalAccounting> query = entityManager.createQuery(jpql, JournalAccounting.class);
query.setParameter("reference", invoiceReference);
query.setParameter("paymentReference", paymentReference);
return query.getSingleResult();
} catch (Exception e) {
// No result found
return null;
}
}
}

View file

@ -5,7 +5,7 @@ lucence.indexDir=${imixs-office.IndexDir}
index.fields=txtsearchstring,txtSubject,txtname,txtEmail,txtUserName,namCreator,txtworkflowgroup,txtworkflowstatus,txtWorkflowAbstract,txtWorkflowSummary,txtworkflowhistory,txtspacename,txtprocessname,_subject,_description,_name,_projectnumber,_projectname,_ordernumber,_contractnumber,datDueDate,txtcommentlog,htmldescription,htmldocumentation,dms,dms_names,invoice.number.stripped,_childitems,$file.names,_VENDOR_NAME,dbtr.number,cdtr.number,invoice.positions,invoice.atc.number
index.fields.analyze=txtUsername
index.fields.noanalyze=type,$UniqueIDRef,$created,$modified,$ModelVersion,$participants,namCreator,$ProcessID,datDate,txtWorkflowGroup,txtemail, datdate, datfrom, datto, numsequencenumber,sequencenumber,dms_count,invoice.number,invoice.number.stripped,invoice.date,invoice.duedate,taxonomy.verteilung.stop,taxonomy.sachpruefung.stop,taxonomy.verteilung.start,taxonomy.sachpruefung.start,taxonomy.buchhaltung.start,taxonomy.buchhaltung.stop,dbtr.number,cdtr.number,payment.date,invoice.total,invoice.currency,invoice.positions,$lasteventdate,payment.type,cdtr.name,document.company,invoice.atc.number
index.fields.store=process.name,txtProcessName,txtWorkflowImageURL,payment.date,invoice.number,invoice.date,invoice.duedate
index.fields.store=process.name,txtProcessName,txtWorkflowImageURL,payment.date,invoice.number,invoice.date,invoice.duedate,invoice.total,invoice.currency,dbtr.name,cdtr.name
index.fields.category=space.name,space.ref,taxonomy.verteilung.stop.by,taxonomy.sachpruefung.stop.by,taxonomy.buchhaltung.stop.by
office.search.noanalyze=invoice.number,invoice.number.stripped,invoice.positions

View file

@ -75,10 +75,10 @@ th { font-weight: bold;}
</bpmn2:participant>
<bpmn2:participant id="Participant_2" name="SEPA-Export Pool" processRef="eingangsrechnung-de"/>
</bpmn2:collaboration>
<bpmn2:process id="eingangsrechnung-de" isExecutable="false" name="Default Process">
<bpmn2:process id="eingangsrechnung-de" isExecutable="false" name="Default Process" processType="Public">
<bpmn2:documentation id="documentation_sxNNHw"/>
</bpmn2:process>
<bpmn2:process definitionalCollaborationRef="Collaboration_1" id="Process_1" isExecutable="false" name="Analyse Debitor ">
<bpmn2:process definitionalCollaborationRef="Collaboration_1" id="Process_1" isExecutable="false" name="Analyse Debitor " processType="Private">
<bpmn2:laneSet id="LaneSet_1" name="Lane Set 1">
<bpmn2:lane id="Lane_1" name="Team">
<bpmn2:flowNodeRef>StartEvent_1</bpmn2:flowNodeRef>
@ -458,8 +458,8 @@ th { font-weight: bold;}
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="association_hwRAUQ" id="BPMNEdge_9JCm5A" sourceElement="BPMNShape_UEsYmw" targetElement="BPMNShape_Task_2">
<di:waypoint x="577.5" y="240.0"/>
<di:waypoint x="577.5" y="265.0"/>
<di:waypoint x="578.0" y="240.0"/>
<di:waypoint x="578.0" y="265.0"/>
<di:waypoint x="625.0" y="265.0"/>
<di:waypoint x="625.0" y="290.0"/>
</bpmndi:BPMNEdge>