///////////////////////////////////////////////////////////////////////////// // // Project ProjectForge Community Edition // www.projectforge.org // // Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de) // // ProjectForge is dual-licensed. // // This community edition 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; version 3 of the License. // // This community edition 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 should have received a copy of the GNU General Public License along // with this program; if not, see http://www.gnu.org/licenses/. // ///////////////////////////////////////////////////////////////////////////// package org.projectforge.fibu; import java.math.BigDecimal; import java.sql.Date; import java.util.ArrayList; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Transient; import org.apache.commons.lang.StringUtils; import org.hibernate.annotations.IndexColumn; import org.hibernate.search.annotations.DateBridge; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Fields; import org.hibernate.search.annotations.Index; import org.hibernate.search.annotations.Indexed; import org.hibernate.search.annotations.IndexedEmbedded; import org.hibernate.search.annotations.Resolution; import org.hibernate.search.annotations.Store; import org.projectforge.common.NumberHelper; import org.projectforge.core.AbstractHistorizableBaseDO; import org.projectforge.core.DefaultBaseDO; import org.projectforge.core.PFPersistancyBehavior; import org.projectforge.user.PFUserDO; /** * Repräsentiert einen Auftrag oder ein Angebot. Ein Angebot kann abgelehnt oder durch ein anderes ersetzt werden, muss also nicht zum * tatsächlichen Auftrag werden. Wichtig ist: Alle Felder sind historisiert, so dass Änderungen wertvolle Informationen enthalten, wie * beispielsweise die Beauftragungshistorie: LOI am 05.03.08 durch Herrn Müller und schriftlich am 04.04.08 durch Beschaffung. * @author Kai Reinhard (k.reinhard@micromata.de) */ @Entity @Indexed @Table(name = "t_fibu_auftrag") public class AuftragDO extends DefaultBaseDO { private static final long serialVersionUID = -3114903689890703366L; @Field(index = Index.UN_TOKENIZED, store = Store.NO) private Integer nummer; /** Dies sind die alten Auftragsnummern oder Kundenreferenzen. */ @Fields({ @Field(index = Index.TOKENIZED, name = "referenz_tokenized", store = Store.NO), @Field(index = Index.UN_TOKENIZED, store = Store.NO)}) private String referenz; @PFPersistancyBehavior(autoUpdateCollectionEntries = true) @IndexedEmbedded(depth = 1) private List<AuftragsPositionDO> positionen = null; @Field(index = Index.TOKENIZED, store = Store.NO) private AuftragsStatus auftragsStatus; @IndexedEmbedded(depth = 1) private PFUserDO contactPerson; @IndexedEmbedded(depth = 1) private KundeDO kunde; @Field(index = Index.TOKENIZED, store = Store.NO) private String kundeText; @IndexedEmbedded(depth = 2) private ProjektDO projekt; @Field(index = Index.TOKENIZED, store = Store.NO) private String titel; @Field(index = Index.TOKENIZED, store = Store.NO) private String bemerkung; @Field(index = Index.TOKENIZED, store = Store.NO) private String statusBeschreibung; @Field(index = Index.UN_TOKENIZED) @DateBridge(resolution = Resolution.DAY) private Date angebotsDatum; @Field(index = Index.UN_TOKENIZED) @DateBridge(resolution = Resolution.DAY) private Date bindungsFrist; private String beauftragungsBeschreibung; private Date beauftragungsDatum; private BigDecimal fakturiertSum = null; protected String uiStatusAsXml; protected AuftragUIStatus uiStatus; @PFPersistancyBehavior(autoUpdateCollectionEntries = true) @IndexedEmbedded(depth = 1) private List<PaymentScheduleDO> paymentSchedules = null; @Field(index = Index.UN_TOKENIZED, store = Store.NO) @DateBridge(resolution = Resolution.DAY) private Date periodOfPerformanceBegin; @Field(index = Index.UN_TOKENIZED, store = Store.NO) @DateBridge(resolution = Resolution.DAY) private Date periodOfPerformanceEnd; static { AbstractHistorizableBaseDO.putNonHistorizableProperty(AuftragDO.class, "uiStatusAsXml", "uiStatus"); } /** * Datum der Angebotslegung. * @return */ @Column(name = "angebots_datum") public Date getAngebotsDatum() { return angebotsDatum; } public AuftragDO setAngebotsDatum(final Date angebotsDatum) { this.angebotsDatum = angebotsDatum; return this; } @Column(name = "bindungs_frist") public Date getBindungsFrist() { return bindungsFrist; } public AuftragDO setBindungsFrist(final Date bindungsFrist) { this.bindungsFrist = bindungsFrist; return this; } /** * Wann wurde beauftragt? Beachte: Alle Felder historisiert, so dass hier ein Datum z. B. mit dem LOI und später das Datum der * schriftlichen Beauftragung steht. */ @Column(name = "beauftragungs_datum") public Date getBeauftragungsDatum() { return beauftragungsDatum; } public AuftragDO setBeauftragungsDatum(final Date beauftragungsDatum) { this.beauftragungsDatum = beauftragungsDatum; return this; } /** * Adds all net sums of the positions (without not ordered positions) and return the total sum. */ @Transient public BigDecimal getNettoSumme() { if (positionen == null) { return BigDecimal.ZERO; } BigDecimal sum = BigDecimal.ZERO; for (final AuftragsPositionDO position : positionen) { final BigDecimal nettoSumme = position.getNettoSumme(); if (nettoSumme != null && position.getStatus() != AuftragsPositionsStatus.NICHT_BEAUFTRAGT) { sum = sum.add(nettoSumme); } } return sum; } /** * Adds all net sums of the positions (only ordered positions) and return the total sum. */ @Transient public BigDecimal getBeauftragtNettoSumme() { if (positionen == null) { return BigDecimal.ZERO; } BigDecimal sum = BigDecimal.ZERO; for (final AuftragsPositionDO position : positionen) { final BigDecimal nettoSumme = position.getNettoSumme(); if (nettoSumme != null && position.getStatus() != null && position.getStatus().isIn(AuftragsPositionsStatus.ABGESCHLOSSEN, AuftragsPositionsStatus.BEAUFTRAGT, AuftragsPositionsStatus.BEAUFTRAGTE_OPTION) == true) { sum = sum.add(nettoSumme); } } return sum; } /** * Auftragsnummer ist eindeutig und wird fortlaufend erzeugt. */ @Column(unique = true, nullable = false) public Integer getNummer() { return nummer; } public AuftragDO setNummer(final Integer nummer) { this.nummer = nummer; return this; } @Column(length = 255) public String getReferenz() { return referenz; } public AuftragDO setReferenz(final String referenz) { this.referenz = referenz; return this; } @Enumerated(EnumType.STRING) @Column(name = "status", length = 30) public AuftragsStatus getAuftragsStatus() { return auftragsStatus; } /** * @return FAKTURIERT if isVollstaendigFakturiert == true, otherwise AuftragsStatus as String. */ @Transient public String getAuftragsStatusAsString() { if (isVollstaendigFakturiert() == true) return "FAKTURIERT"; return auftragsStatus != null ? auftragsStatus.toString() : null; } public AuftragDO setAuftragsStatus(final AuftragsStatus auftragsStatus) { this.auftragsStatus = auftragsStatus; return this; } /** * Wer hat wann und wie beauftragt? Z. B. Beauftragung per E-Mail durch Herrn Müller. * @return */ @Column(name = "beauftragungs_beschreibung", length = 4000) public String getBeauftragungsBeschreibung() { return beauftragungsBeschreibung; } public AuftragDO setBeauftragungsBeschreibung(final String beauftragungsBeschreibung) { this.beauftragungsBeschreibung = beauftragungsBeschreibung; return this; } @Column(length = 4000, name = "status_beschreibung") public String getStatusBeschreibung() { return statusBeschreibung; } public AuftragDO setStatusBeschreibung(final String statusBeschreibung) { this.statusBeschreibung = statusBeschreibung; return this; } @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "kunde_fk", nullable = true) public KundeDO getKunde() { return kunde; } public AuftragDO setKunde(final KundeDO kunde) { this.kunde = kunde; return this; } @Transient public Integer getKundeId() { if (this.kunde == null) return null; return kunde.getId(); } /** * @see ProjektFormatter#formatProjektKundeAsString(ProjektDO, KundeDO, String) */ @Transient public String getProjektKundeAsString() { return ProjektFormatter.formatProjektKundeAsString(this.projekt, this.kunde, this.kundeText); } /** * @see KundeFormatter#formatKundeAsString(KundeDO, String) */ @Transient public String getKundeAsString() { return KundeFormatter.formatKundeAsString(this.kunde, this.kundeText); } @Transient public String getProjektAsString() { final StringBuffer buf = new StringBuffer(); boolean first = true; if (this.projekt != null) { if (projekt.getKunde() != null) { if (first == true) first = false; else buf.append("; "); buf.append(projekt.getKunde().getName()); } if (StringUtils.isNotBlank(projekt.getName()) == true) { if (first == true) first = false; else buf.append(" - "); buf.append(projekt.getName()); } } return buf.toString(); } /** * Freitextfeld, falls Kunde nicht aus Liste gewählt werden kann bzw. für Rückwärtskompatibilität mit alten Kunden. */ @Column(name = "kunde_text", length = 1000) public String getKundeText() { return kundeText; } public AuftragDO setKundeText(final String kundeText) { this.kundeText = kundeText; return this; } @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "projekt_fk", nullable = true) public ProjektDO getProjekt() { return projekt; } public AuftragDO setProjekt(final ProjektDO projekt) { this.projekt = projekt; return this; } @Transient public Integer getProjektId() { if (this.projekt == null) return null; return projekt.getId(); } public AuftragDO setTitel(final String titel) { this.titel = titel; return this; } @Column(name = "titel", length = 1000) public String getTitel() { return titel; } @Column(length = 4000) public String getBemerkung() { return bemerkung; } public AuftragDO setBemerkung(final String bemerkung) { this.bemerkung = bemerkung; return this; } @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "contact_person_fk", nullable = true) public PFUserDO getContactPerson() { return contactPerson; } public AuftragDO setContactPerson(final PFUserDO contactPerson) { this.contactPerson = contactPerson; return this; } @Transient public Integer getContactPersonId() { if (this.contactPerson == null) return null; return contactPerson.getId(); } /** * @return true wenn alle Auftragspositionen vollständig fakturiert sind. * @see AuftragsPositionDO#isVollstaendigFakturiert() */ @Transient public boolean isVollstaendigFakturiert() { if (positionen == null || auftragsStatus != AuftragsStatus.ABGESCHLOSSEN) { return false; } for (final AuftragsPositionDO position : positionen) { if (position.isVollstaendigFakturiert() == false && (position.getStatus() == null || position.getStatus().isIn(AuftragsPositionsStatus.NICHT_BEAUFTRAGT) == false)) { return false; } } return true; } @Transient public boolean isAbgeschlossenUndNichtVollstaendigFakturiert() { if (getAuftragsStatus().isIn(AuftragsStatus.ABGESCHLOSSEN) == true && isVollstaendigFakturiert() == false) { return true; } if (getPositionen() != null) { for (final AuftragsPositionDO pos : getPositionen()) { if (pos.getStatus() == AuftragsPositionsStatus.ABGESCHLOSSEN && pos.isVollstaendigFakturiert() == false) { return true; } } } return false; } /** * Get the position entries for this object. */ @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "auftrag") @IndexColumn(name = "number", base = 1) public List<AuftragsPositionDO> getPositionen() { return this.positionen; } /** * @param number * @return AuftragsPositionDO with given position number or null (iterates through the list of positions and compares the number), if not * exist. */ public AuftragsPositionDO getPosition(final short number) { if (positionen == null) { return null; } for (final AuftragsPositionDO position : this.positionen) { if (position.getNumber() == number) { return position; } } return null; } public AuftragDO setPositionen(final List<AuftragsPositionDO> positionen) { this.positionen = positionen; return this; } public AuftragDO addPosition(final AuftragsPositionDO position) { ensureAndGetPositionen(); short number = 1; for (final AuftragsPositionDO pos : positionen) { if (pos.getNumber() >= number) { number = pos.getNumber(); number++; } } position.setNumber(number); position.setAuftrag(this); this.positionen.add(position); return this; } public List<AuftragsPositionDO> ensureAndGetPositionen() { if (this.positionen == null) { setPositionen(new ArrayList<AuftragsPositionDO>()); } return getPositionen(); } /** * @return The sum of person days of all positions. */ @Transient public BigDecimal getPersonDays() { BigDecimal result = BigDecimal.ZERO; if (this.positionen != null) { for (final AuftragsPositionDO pos : this.positionen) { if (pos.getPersonDays() != null) { result = result.add(pos.getPersonDays()); } } } return result; } /** * Sums all positions. Must be set in all positions before usage. The value is not calculated automatically! * @see AuftragDao#calculateInvoicedSum(java.util.Collection) */ @Transient public BigDecimal getFakturiertSum() { if (this.fakturiertSum == null) { this.fakturiertSum = BigDecimal.ZERO; if (positionen != null) { for (final AuftragsPositionDO pos : positionen) { if (NumberHelper.isNotZero(pos.getFakturiertSum()) == true) { this.fakturiertSum = this.fakturiertSum.add(pos.getFakturiertSum()); } } } } return this.fakturiertSum; } public AuftragDO setFakturiertSum(final BigDecimal fakturiertSum) { this.fakturiertSum = fakturiertSum; return this; } @Transient public BigDecimal getZuFakturierenSum() { BigDecimal val = BigDecimal.ZERO; if (positionen != null) { for (final AuftragsPositionDO pos : positionen) { if (pos.getStatus() == null || pos.getStatus().isIn(AuftragsPositionsStatus.ABGESCHLOSSEN) == false) { continue; } BigDecimal net = pos.getNettoSumme(); if (net == null) { net = BigDecimal.ZERO; } BigDecimal invoiced = pos.getFakturiertSum(); if (invoiced == null) { invoiced = BigDecimal.ZERO; } val = val.add(net).subtract(invoiced); } } return val; } /** * The user interface status of an order. The {@link AuftragUIStatus} is stored as XML. * @return the XML representation of the uiStatus. * @see AuftragUIStatus */ @Column(name = "ui_status_as_xml", length = 10000) public String getUiStatusAsXml() { return uiStatusAsXml; } /** * @param uiStatus the uiStatus to set * @return this for chaining. */ public AuftragDO setUiStatusAsXml(final String uiStatus) { this.uiStatusAsXml = uiStatus; return this; } /** * @return the rechungUiStatus */ @Transient public AuftragUIStatus getUiStatus() { if (uiStatus == null) { uiStatus = new AuftragUIStatus(); } return uiStatus; } /** * @param uiStatus the rechungUiStatus to set * @return this for chaining. */ public AuftragDO setUiStatus(final AuftragUIStatus uiStatus) { this.uiStatus = uiStatus; return this; } /** * Get the payment schedule entries for this object. */ @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "auftrag") @IndexColumn(name = "number", base = 1) public List<PaymentScheduleDO> getPaymentSchedules() { return this.paymentSchedules; } /** * @param number * @return PaymentScheduleDO with given position number or null (iterates through the list of payment schedules and compares the number), if not * exist. */ public PaymentScheduleDO getPaymentSchedule(final short number) { if (paymentSchedules == null) { return null; } for (final PaymentScheduleDO schedule : this.paymentSchedules) { if (schedule.getNumber() == number) { return schedule; } } return null; } public AuftragDO setPaymentSchedules(final List<PaymentScheduleDO> paymentSchedules) { this.paymentSchedules = paymentSchedules; return this; } public AuftragDO addPaymentSchedule(final PaymentScheduleDO paymentSchedule) { ensureAndGetPaymentSchedules(); short number = 1; for (final PaymentScheduleDO pos : paymentSchedules) { if (pos.getNumber() >= number) { number = pos.getNumber(); number++; } } paymentSchedule.setNumber(number); paymentSchedule.setAuftrag(this); this.paymentSchedules.add(paymentSchedule); return this; } public List<PaymentScheduleDO> ensureAndGetPaymentSchedules() { if (this.paymentSchedules == null) { setPaymentSchedules(new ArrayList<PaymentScheduleDO>()); } return getPaymentSchedules(); } @Transient public boolean isZahlplanAbgeschlossenUndNichtVollstaendigFakturiert() { if (getPaymentSchedules() != null) { for (final PaymentScheduleDO pos : getPaymentSchedules()) { if (pos.isReached() == true && pos.isVollstaendigFakturiert() == false) { return true; } } } return false; } /** * @return the timeOfPerformanceBegin */ @Column(name = "period_of_performance_begin") public Date getPeriodOfPerformanceBegin() { return periodOfPerformanceBegin; } /** * @param periodOfPerformanceBegin the periodOfPerformanceBegin to set * @return this for chaining. */ public AuftragDO setPeriodOfPerformanceBegin(final Date periodOfPerformanceBegin) { this.periodOfPerformanceBegin = periodOfPerformanceBegin; return this; } /** * @return the timeOfPerformanceEnd */ @Column(name = "period_of_performance_end") public Date getPeriodOfPerformanceEnd() { return periodOfPerformanceEnd; } /** * @param periodOfPerformanceEnd the periodOfPerformanceEnd to set * @return this for chaining. */ public AuftragDO setPeriodOfPerformanceEnd(final Date periodOfPerformanceEnd) { this.periodOfPerformanceEnd = periodOfPerformanceEnd; return this; } }