///////////////////////////////////////////////////////////////////////////// // // 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.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.Predicate; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.hibernate.Criteria; import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; import org.projectforge.access.OperationType; import org.projectforge.common.DatabaseDialect; import org.projectforge.common.DateHelper; import org.projectforge.common.NumberHelper; import org.projectforge.core.BaseDao; import org.projectforge.core.BaseSearchFilter; import org.projectforge.core.ConfigXml; import org.projectforge.core.DisplayHistoryEntry; import org.projectforge.core.MessageParam; import org.projectforge.core.MessageParamType; import org.projectforge.core.QueryFilter; import org.projectforge.core.UserException; import org.projectforge.database.HibernateUtils; import org.projectforge.database.SQLHelper; import org.projectforge.mail.Mail; import org.projectforge.mail.SendMail; import org.projectforge.task.TaskDO; import org.projectforge.task.TaskDao; import org.projectforge.task.TaskTree; import org.projectforge.user.PFUserDO; import org.projectforge.user.UserDao; import org.projectforge.user.UserRightId; import org.projectforge.xml.stream.XmlObjectReader; import org.projectforge.xml.stream.XmlObjectWriter; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; public class AuftragDao extends BaseDao<AuftragDO> { public static final UserRightId USER_RIGHT_ID = UserRightId.PM_ORDER_BOOK; public final static int START_NUMBER = 1; private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(AuftragDao.class); private static final Class< ? >[] ADDITIONAL_HISTORY_SEARCH_DOS = new Class[] { AuftragsPositionDO.class}; private static final String[] ADDITIONAL_SEARCH_FIELDS = new String[] { "contactPerson.username", "contactPerson.firstname", "contactPerson.lastname", "kunde.name", "projekt.name", "projekt.kunde.name", "positionen.position", "positionen.art", "positionen.status", "positionen.titel", "positionen.bemerkung", "positionen.nettoSumme"}; private UserDao userDao; private KundeDao kundeDao; private ProjektDao projektDao; private SendMail sendMail; private Integer abgeschlossenNichtFakturiert; private RechnungCache rechnungCache; private TaskDao taskDao; private TaskTree taskTree; public void setUserDao(final UserDao userDao) { this.userDao = userDao; } public void setKundeDao(final KundeDao kundeDao) { this.kundeDao = kundeDao; } public void setProjektDao(final ProjektDao projektDao) { this.projektDao = projektDao; } public void setRechnungCache(final RechnungCache rechnungCache) { this.rechnungCache = rechnungCache; } public void setTaskDao(final TaskDao taskDao) { this.taskDao = taskDao; } /** * Could not use injection by spring, because TaskTree is already injected in AuftragDao. * @param taskTree */ public void registerTaskTree(final TaskTree taskTree) { this.taskTree = taskTree; } public void setSendMail(final SendMail sendMail) { this.sendMail = sendMail; } public AuftragDao() { super(AuftragDO.class); userRightId = USER_RIGHT_ID; } @Override protected String[] getAdditionalSearchFields() { return ADDITIONAL_SEARCH_FIELDS; } /** * List of all years with invoices: select min(datum), max(datum) from t_fibu_rechnung. * @return */ @SuppressWarnings("unchecked") public int[] getYears() { final List<Object[]> list = getSession().createQuery("select min(angebotsDatum), max(angebotsDatum) from AuftragDO t").list(); return SQLHelper.getYears(list); } /** * @return Map with all order positions referencing a task. The key of the map is the task id. */ public Map<Integer, Set<AuftragsPositionVO>> getTaskReferences() { final Map<Integer, Set<AuftragsPositionVO>> result = new HashMap<Integer, Set<AuftragsPositionVO>>(); @SuppressWarnings("unchecked") final List<AuftragsPositionDO> list = getHibernateTemplate().find("from AuftragsPositionDO a where a.task.id is not null"); if (list == null) { return result; } for (final AuftragsPositionDO pos : list) { if (pos.getTaskId() == null) { log.error("Oups, should not occur, that in getTaskReference a order position without a task reference is found."); continue; } final AuftragsPositionVO vo = new AuftragsPositionVO(pos); Set<AuftragsPositionVO> set = result.get(pos.getTaskId()); if (set == null) { set = new TreeSet<AuftragsPositionVO>(); result.put(pos.getTaskId(), set); } set.add(vo); } return result; } public AuftragsStatistik buildStatistik(final List<AuftragDO> list) { final AuftragsStatistik stats = new AuftragsStatistik(); if (list == null) { return stats; } for (final AuftragDO auftrag : list) { calculateInvoicedSum(auftrag); stats.add(auftrag); } return stats; } /** * Get all invoices and set the field fakturiertSum for every order of the given col. * @param col * @see RechnungCache#getRechnungsPositionVOSetByAuftragsPositionId(Integer) */ public void calculateInvoicedSum(final Collection<AuftragDO> col) { if (col == null) { return; } for (final AuftragDO auftrag : col) { calculateInvoicedSum(auftrag); } } /** * Get all invoices and set the field fakturiertSum for the given order. * @param order * @see RechnungCache#getRechnungsPositionVOSetByAuftragsPositionId(Integer) */ public void calculateInvoicedSum(final AuftragDO order) { if (order == null) { return; } if (order.getPositionen() != null) { for (final AuftragsPositionDO pos : order.getPositionen()) { final Set<RechnungsPositionVO> set = rechnungCache.getRechnungsPositionVOSetByAuftragsPositionId(pos.getId()); if (set != null) { pos.setFakturiertSum(RechnungDao.getNettoSumme(set)); } } } } /** * @param auftrag * @param contactPersonId If null, then contact person will be set to null; * @see BaseDao#getOrLoad(Integer) */ public void setContactPerson(final AuftragDO auftrag, final Integer contactPersonId) { if (contactPersonId == null) { auftrag.setContactPerson(null); } else { final PFUserDO contactPerson = userDao.getOrLoad(contactPersonId); auftrag.setContactPerson(contactPerson); } } /** * @param position * @param taskId * @see BaseDao#getOrLoad(Integer) */ public void setTask(final AuftragsPositionDO position, final Integer taskId) { final TaskDO task = taskDao.getOrLoad(taskId); position.setTask(task); } /** * @param auftrag * @param kundeId If null, then kunde will be set to null; * @see BaseDao#getOrLoad(Integer) */ public void setKunde(final AuftragDO auftrag, final Integer kundeId) { final KundeDO kunde = kundeDao.getOrLoad(kundeId); auftrag.setKunde(kunde); } /** * @param auftrag * @param projektId If null, then projekt will be set to null; * @see BaseDao#getOrLoad(Integer) */ public void setProjekt(final AuftragDO auftrag, final Integer projektId) { final ProjektDO projekt = projektDao.getOrLoad(projektId); auftrag.setProjekt(projekt); } /** * @param posString Format ###.## (<order number>.<position number>). */ @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public AuftragsPositionDO getAuftragsPosition(final String posString) { Integer auftragsNummer = null; Short positionNummer = null; if (posString == null) { return null; } final int sep = posString.indexOf('.'); if (sep <= 0 || sep + 1 >= posString.length()) { return null; } auftragsNummer = NumberHelper.parseInteger(posString.substring(0, posString.indexOf('.'))); positionNummer = NumberHelper.parseShort(posString.substring(posString.indexOf('.') + 1)); if (auftragsNummer == null || positionNummer == null) { log.info("Cannot parse order number (format ###.## expected: " + posString); return null; } @SuppressWarnings("unchecked") final List<AuftragDO> list = getHibernateTemplate().find("from AuftragDO k where k.nummer=?", auftragsNummer); if (CollectionUtils.isEmpty(list) == true) { return null; } return list.get(0).getPosition(positionNummer); } public synchronized int getAbgeschlossenNichtFakturiertAnzahl() { if (abgeschlossenNichtFakturiert != null) { return abgeschlossenNichtFakturiert; } final AuftragFilter filter = new AuftragFilter(); filter.setListType(AuftragFilter.FILTER_ABGESCHLOSSEN_NF); try { final List<AuftragDO> list = getList(filter, false); abgeschlossenNichtFakturiert = list != null ? list.size() : 0; return abgeschlossenNichtFakturiert; } catch (final Exception ex) { log.error("Exception ocurred while getting number of closed and not invoiced orders: " + ex.getMessage(), ex); // Exception e. g. if data-base update is needed. return 0; } } @Override public List<AuftragDO> getList(final BaseSearchFilter filter) { return getList(filter, true); } private List<AuftragDO> getList(final BaseSearchFilter filter, final boolean checkAccess) { final AuftragFilter myFilter; if (filter instanceof AuftragFilter) { myFilter = (AuftragFilter) filter; } else { myFilter = new AuftragFilter(filter); } final QueryFilter queryFilter = new QueryFilter(myFilter); Boolean vollstaendigFakturiert = null; if (myFilter.isShowBeauftragtNochNichtVollstaendigFakturiert() == true) { queryFilter.add(Restrictions.not(Restrictions.in("auftragsStatus", new AuftragsStatus[] { AuftragsStatus.ABGELEHNT, AuftragsStatus.ERSETZT, AuftragsStatus.GELEGT, AuftragsStatus.GROB_KALKULATION, AuftragsStatus.IN_ERSTELLUNG}))); vollstaendigFakturiert = false; } else if (myFilter.isShowNochNichtVollstaendigFakturiert() == true) { queryFilter.add(Restrictions.not(Restrictions.in("auftragsStatus", new AuftragsStatus[] { AuftragsStatus.ABGELEHNT, AuftragsStatus.ERSETZT}))); vollstaendigFakturiert = false; } else if (myFilter.isShowVollstaendigFakturiert() == true) { vollstaendigFakturiert = true; } else if (myFilter.isShowAbgelehnt() == true) { queryFilter.add(Restrictions.eq("auftragsStatus", AuftragsStatus.ABGELEHNT)); } else if (myFilter.isShowAbgeschlossenNichtFakturiert() == true) { queryFilter .createAlias("positionen", "position") .createAlias("paymentSchedules", "paymentSchedule", Criteria.FULL_JOIN) .add( Restrictions.or( Restrictions.or(Restrictions.eq("auftragsStatus", AuftragsStatus.ABGESCHLOSSEN), Restrictions.eq("position.status", AuftragsPositionsStatus.ABGESCHLOSSEN)), Restrictions.eq("paymentSchedule.reached", true))); vollstaendigFakturiert = false; } else if (myFilter.isShowAkquise() == true) { queryFilter.add(Restrictions.in("auftragsStatus", new AuftragsStatus[] { AuftragsStatus.GELEGT, AuftragsStatus.IN_ERSTELLUNG, AuftragsStatus.GROB_KALKULATION})); } else if (myFilter.isShowBeauftragt() == true) { queryFilter.add(Restrictions.in("auftragsStatus", new AuftragsStatus[] { AuftragsStatus.BEAUFTRAGT, AuftragsStatus.LOI, AuftragsStatus.ESKALATION})); } else if (myFilter.isShowErsetzt() == true) { queryFilter.add(Restrictions.eq("auftragsStatus", AuftragsStatus.ERSETZT)); } if (myFilter.getYear() > 1900) { final Calendar cal = DateHelper.getUTCCalendar(); cal.set(Calendar.YEAR, myFilter.getYear()); java.sql.Date lo = null; java.sql.Date hi = null; cal.set(Calendar.DAY_OF_YEAR, 1); lo = new java.sql.Date(cal.getTimeInMillis()); final int lastDayOfYear = cal.getActualMaximum(Calendar.DAY_OF_YEAR); cal.set(Calendar.DAY_OF_YEAR, lastDayOfYear); hi = new java.sql.Date(cal.getTimeInMillis()); queryFilter.add(Restrictions.between("angebotsDatum", lo, hi)); } queryFilter.addOrder(Order.desc("nummer")); final List<AuftragDO> list; if (checkAccess == true) { list = getList(queryFilter); } else { list = internalGetList(queryFilter); } if (vollstaendigFakturiert != null || myFilter.getAuftragsPositionsArt() != null) { final Boolean invoiced = vollstaendigFakturiert; final AuftragFilter fil = myFilter; CollectionUtils.filter(list, new Predicate() { public boolean evaluate(final Object object) { final AuftragDO auftrag = (AuftragDO) object; if (fil.getAuftragsPositionsArt() != null) { boolean match = false; if (CollectionUtils.isNotEmpty(auftrag.getPositionen()) == true) { for (final AuftragsPositionDO position : auftrag.getPositionen()) { if (fil.getAuftragsPositionsArt() == position.getArt()) { match = true; break; } } } if (match == false) { return false; } } final boolean orderIsCompletelyInvoiced = auftrag.isVollstaendigFakturiert(); if (HibernateUtils.getDialect() != DatabaseDialect.HSQL && myFilter.isShowAbgeschlossenNichtFakturiert() == true) { // if order is completed and not all positions are completely invoiced if (auftrag.getAuftragsStatus() == AuftragsStatus.ABGESCHLOSSEN && orderIsCompletelyInvoiced == false) { return true; } // if order is completed and not completely invoiced if (auftrag.getPositionen() != null) { for (final AuftragsPositionDO pos : auftrag.getPositionen()) { if (pos.isAbgeschlossenUndNichtVollstaendigFakturiert() == true) { return true; } } } if (auftrag.getPaymentSchedules() != null) { for (final PaymentScheduleDO schedule : auftrag.getPaymentSchedules()) { if (schedule.isReached() == true && schedule.isVollstaendigFakturiert() == false) { return true; } } } return false; } return orderIsCompletelyInvoiced == invoiced; } }); } return list; } @SuppressWarnings("unchecked") @Override protected void onSaveOrModify(final AuftragDO obj) { if (obj.getNummer() == null) { throw new UserException("validation.required.valueNotPresent", new MessageParam("fibu.auftrag.nummer", MessageParamType.I18N_KEY)); } if (obj.getId() == null) { // Neuer Auftrag/Angebot final Integer next = getNextNumber(obj); if (next.intValue() != obj.getNummer().intValue()) { throw new UserException("fibu.auftrag.error.nummerIstNichtFortlaufend"); } } else { final List<RechnungDO> list = getHibernateTemplate().find("from AuftragDO r where r.nummer = ? and r.id <> ?", new Object[] { obj.getNummer(), obj.getId()}); if (list != null && list.size() > 0) { throw new UserException("fibu.auftrag.error.nummerBereitsVergeben"); } } if (CollectionUtils.isEmpty(obj.getPositionen()) == true) { throw new UserException("fibu.auftrag.error.auftragHatKeinePositionen"); } final int size = obj.getPositionen().size(); for (int i = size - 1; i > 0; i--) { // Don't remove first position, remove only the last empty positions. final AuftragsPositionDO position = obj.getPositionen().get(i); if (position.getId() == null && position.isEmpty() == true) { obj.getPositionen().remove(i); } else { break; } } if (CollectionUtils.isNotEmpty(obj.getPositionen()) == true) { for (final AuftragsPositionDO position : obj.getPositionen()) { position.checkVollstaendigFakturiert(); } } abgeschlossenNichtFakturiert = null; final String uiStatusAsXml = XmlObjectWriter.writeAsXml(obj.getUiStatus()); obj.setUiStatusAsXml(uiStatusAsXml); final List<PaymentScheduleDO> paymentSchedules = obj.getPaymentSchedules(); final int pmSize = paymentSchedules != null ? paymentSchedules.size() : -1; if (pmSize > 1) { for (int i = pmSize - 1; i > 0; i--) { // Don't remove first payment schedule, remove only the last empty payment schedules. final PaymentScheduleDO schedule = obj.getPaymentSchedules().get(i); if (schedule.getId() == null && schedule.isEmpty() == true) { obj.getPaymentSchedules().remove(i); } else { break; } } } } @Override protected void afterSaveOrModify(final AuftragDO obj) { super.afterSaveOrModify(obj); if (taskTree != null) { taskTree.refreshOrderPositionReferences(); } } @Override protected void afterLoad(final AuftragDO obj) { final XmlObjectReader reader = new XmlObjectReader(); reader.initialize(AuftragUIStatus.class); final String styleAsXml = obj.getUiStatusAsXml(); final AuftragUIStatus status; if (StringUtils.isEmpty(styleAsXml) == true) { status = new AuftragUIStatus(); } else { status = (AuftragUIStatus) reader.read(styleAsXml); } obj.setUiStatus(status); } /** * @see org.projectforge.core.BaseDao#prepareHibernateSearch(org.projectforge.core.ExtendedBaseDO, org.projectforge.access.OperationType) */ @Override protected void prepareHibernateSearch(final AuftragDO obj, final OperationType operationType) { projektDao.initializeProjektManagerGroup(obj.getProjekt()); } /** * Sends an e-mail to the projekt manager if exists and is not equals to the logged in user. * @param auftrag * @param operationType * @return */ public boolean sendNotificationIfRequired(final AuftragDO auftrag, final OperationType operationType, final String requestUrl) { if (ConfigXml.getInstance().isSendMailConfigured() == false) { return false; } final PFUserDO contactPerson = auftrag.getContactPerson(); if (contactPerson == null) { return false; } if (hasAccess(contactPerson, auftrag, null, OperationType.SELECT, false) == false) { return false; } final Map<String, Object> data = new HashMap<String, Object>(); data.put("contactPerson", contactPerson); data.put("auftrag", auftrag); data.put("requestUrl", requestUrl); final List<DisplayHistoryEntry> history = getDisplayHistoryEntries(auftrag); final List<DisplayHistoryEntry> list = new ArrayList<DisplayHistoryEntry>(); int i = 0; for (final DisplayHistoryEntry entry : history) { list.add(entry); if (++i >= 10) { break; } } data.put("history", list); final Mail msg = new Mail(); msg.setTo(contactPerson); final String subject; if (operationType == OperationType.INSERT) { subject = "Auftrag #" + auftrag.getNummer() + " wurde angelegt."; } else if (operationType == OperationType.DELETE) { subject = "Auftrag #" + auftrag.getNummer() + " wurde gelöscht."; } else { subject = "Auftrag #" + auftrag.getNummer() + " wurde geändert."; } msg.setProjectForgeSubject(subject); data.put("subject", subject); final String content = sendMail.renderGroovyTemplate(msg, "mail/orderChangeNotification.html", data, contactPerson); msg.setContent(content); msg.setContentType(Mail.CONTENTTYPE_HTML); return sendMail.send(msg, null, null); } /** * Gets the highest Auftragsnummer. * @param auftrag wird benötigt, damit geschaut werden kann, ob dieser Auftrag ggf. schon existiert. Wenn er schon eine Nummer hatte, so * kann verhindert werden, dass er eine nächst höhere Nummer bekommt. Ein solcher Auftrag bekommt die alte Nummer wieder * zugeordnet. */ @SuppressWarnings("unchecked") @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public Integer getNextNumber(final AuftragDO auftrag) { if (auftrag.getId() != null) { final AuftragDO orig = internalGetById(auftrag.getId()); if (orig.getNummer() != null) { auftrag.setNummer(orig.getNummer()); return orig.getNummer(); } } final List<Integer> list = getSession().createQuery("select max(t.nummer) from AuftragDO t").list(); Validate.notNull(list); if (list.size() == 0 || list.get(0) == null) { log.info("First entry of AuftragDO"); return START_NUMBER; } Integer number = list.get(0); return ++number; } /** * Gets history entries of super and adds all history entries of the AuftragsPositionDO childs. * @see org.projectforge.core.BaseDao#getDisplayHistoryEntries(org.projectforge.core.ExtendedBaseDO) */ @Override public List<DisplayHistoryEntry> getDisplayHistoryEntries(final AuftragDO obj) { final List<DisplayHistoryEntry> list = super.getDisplayHistoryEntries(obj); if (hasLoggedInUserHistoryAccess(obj, false) == false) { return list; } if (CollectionUtils.isNotEmpty(obj.getPositionen()) == true) { for (final AuftragsPositionDO position : obj.getPositionen()) { final List<DisplayHistoryEntry> entries = internalGetDisplayHistoryEntries(position); for (final DisplayHistoryEntry entry : entries) { final String propertyName = entry.getPropertyName(); if (propertyName != null) { entry.setPropertyName("Pos#" + position.getNumber() + ":" + entry.getPropertyName()); // Prepend number of positon. } else { entry.setPropertyName("Pos#" + position.getNumber()); } } list.addAll(entries); } } if (CollectionUtils.isNotEmpty(obj.getPaymentSchedules()) == true) { for (final PaymentScheduleDO schedule : obj.getPaymentSchedules()) { final List<DisplayHistoryEntry> entries = internalGetDisplayHistoryEntries(schedule); for (final DisplayHistoryEntry entry : entries) { final String propertyName = entry.getPropertyName(); if (propertyName != null) { entry.setPropertyName("PaymentSchedule#" + schedule.getNumber() + ":" + entry.getPropertyName()); // Prepend number of positon. } else { entry.setPropertyName("PaymentSchedule#" + schedule.getNumber()); } } list.addAll(entries); } } Collections.sort(list, new Comparator<DisplayHistoryEntry>() { public int compare(final DisplayHistoryEntry o1, final DisplayHistoryEntry o2) { return (o2.getTimestamp().compareTo(o1.getTimestamp())); } }); return list; } @Override protected Class< ? >[] getAdditionalHistorySearchDOs() { return ADDITIONAL_HISTORY_SEARCH_DOS; } /** * Returns also true, if idSet contains the id of any order position. * @see org.projectforge.core.BaseDao#contains(java.util.Set, org.projectforge.core.ExtendedBaseDO) */ @Override protected boolean contains(final Set<Integer> idSet, final AuftragDO entry) { if (super.contains(idSet, entry) == true) { return true; } for (final AuftragsPositionDO pos : entry.getPositionen()) { if (idSet.contains(pos.getId()) == true) { return true; } } return false; } @Override public AuftragDO newInstance() { return new AuftragDO(); } /** * @see org.projectforge.core.BaseDao#useOwnCriteriaCacheRegion() */ @Override protected boolean useOwnCriteriaCacheRegion() { return true; } }