BrightSide Workbench Full Report + Source Code
Issue.java
Go to the documentation of this file.
1 /*
2  * TurrĂ³ i Cutiller Foundation. License notice.
3  * Copyright (C) 2011 Lluis TurrĂ³ Cutiller <http://www.turro.org/>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Affero General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Affero General Public License for more details.
14  *
15  * You should have received a copy of the GNU Affero General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 package org.turro.dossier.entity;
19 
20 import java.util.Collection;
21 import java.util.Date;
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.Set;
25 import javax.persistence.*;
26 import org.turro.string.Strings;
27 import org.turro.action.Contacts;
28 import org.turro.action.Interceptors;
29 import org.turro.dossier.db.DossierPU;
30 import org.turro.dossier.dossier.ParticipantSet;
31 import org.turro.jpa.Dao;
32 import org.turro.jpa.entity.IDaoEntity;
33 import org.turro.math.Round;
34 import org.turro.plugin.contacts.IContact;
35 import org.turro.reflection.MappingSet;
36 import org.turro.related.RelatedItem;
37 import org.turro.util.Chars;
38 
43 @Entity
44 public class Issue implements java.io.Serializable, IDaoEntity {
45 
46  @Id
47  @GeneratedValue(strategy=GenerationType.IDENTITY)
48  @Column(name="IDENTIFIER")
49  private Long id;
50 
51  @Temporal(value = TemporalType.TIMESTAMP)
52  private java.util.Date startDate;
53 
54  @Temporal(value = TemporalType.TIMESTAMP)
55  private java.util.Date controlDate;
56 
57  @Temporal(value = TemporalType.TIMESTAMP)
58  private java.util.Date delivery;
59 
60  @Temporal(value = TemporalType.TIMESTAMP)
61  private java.util.Date issueDate;
62 
63  @Temporal(value = TemporalType.TIMESTAMP)
64  private java.util.Date modification;
65 
66  @Temporal(value = TemporalType.TIMESTAMP)
67  private java.util.Date solvedDate;
68 
69  @Lob
70  @Column(length=4096)
71  private String description, grouping;
72 
73  @Column(name="ISSUE_TYPE")
74  private IssueType type;
75 
76  private IssuePriority priority;
77 
78  private IssueResolution resolution;
79 
80  private IssueStatus status;
81 
82  private double expenses, hours, price;
83 
84  private boolean publishable, milestone;
85 
86  @OneToOne
87  private Issue duplicated;
88 
89  @ManyToOne
90  @JoinColumn(name="DOSSIER_FK")
91  private Dossier dossier;
92 
93  @ManyToOne
94  private DossierVersion version;
95 
96  @OneToMany(mappedBy = "issue", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval=true)
97  @OrderBy(value="name")
98  private Set<IssueParticipant> participants = new HashSet<IssueParticipant>();
99 
100  @OneToMany(mappedBy = "issue", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval=true)
101  @OrderBy(value = "modification ASC")
102  private Set<IssueComment> comments = new HashSet<IssueComment>();
103 
104  @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval=true)
105  @OrderBy(value="type")
106  private Set<IssuePredecessor> sources = new HashSet<IssuePredecessor>();
107 
108  @OneToMany(mappedBy = "source", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval=true)
109  @OrderBy(value="type")
110  private Set<IssuePredecessor> targets = new HashSet<IssuePredecessor>();
111 
112  private transient RelatedItem related = new RelatedItem();
113 
114  public Set<IssueParticipant> getParticipants() {
115  return participants;
116  }
117 
118  public void setParticipants(Set<IssueParticipant> participants) {
119  this.participants = participants;
120  }
121 
122  public Set<IssueComment> getComments() {
123  Dao dao = new DossierPU();
124  if(id != null && id > 0 && dao.isNotLoaded(comments)) {
125  comments = dao.lazyLoader(Issue.class, this, "comments").comments;
126  }
127  return comments;
128  }
129 
130  public void setComments(Set<IssueComment> comments) {
131  this.comments = comments;
132  }
133 
134  public Date getControlDate() {
135  return controlDate;
136  }
137 
138  public void setControlDate(Date controlDate) {
139  this.controlDate = controlDate;
140  }
141 
142  public Date getDelivery() {
143  return delivery;
144  }
145 
146  public void setDelivery(Date delivery) {
147  this.delivery = delivery;
148  }
149 
150  public String getDescription() {
151  return description;
152  }
153 
154  public void setDescription(String description) {
155  this.description = description;
156  }
157 
158  public Dossier getDossier() {
159  return dossier;
160  }
161 
162  public void setDossier(Dossier dossier) {
163  this.dossier = dossier;
164  }
165 
166  public Issue getDuplicated() {
167  return duplicated;
168  }
169 
170  public void setDuplicated(Issue duplicated) {
171  this.duplicated = duplicated;
172  }
173 
174  public Long getId() {
175  return id;
176  }
177 
178  public void setId(Long id) {
179  this.id = id;
180  }
181 
182  public Date getIssueDate() {
183  return issueDate;
184  }
185 
186  public void setIssueDate(Date issueDate) {
187  this.issueDate = issueDate;
188  }
189 
190  public boolean isMilestone() {
191  return milestone;
192  }
193 
194  public void setMilestone(boolean milestone) {
195  this.milestone = milestone;
196  }
197 
198  public Date getModification() {
199  return modification;
200  }
201 
202  public void setModification(Date modification) {
203  this.modification = modification;
204  }
205 
207  return priority;
208  }
209 
210  public void setPriority(IssuePriority priority) {
211  this.priority = priority;
212  }
213 
214  public boolean isPublishable() {
215  return publishable;
216  }
217 
218  public void setPublishable(boolean publishable) {
219  this.publishable = publishable;
220  }
221 
223  return related;
224  }
225 
227  return resolution;
228  }
229 
230  public void setResolution(IssueResolution resolution) {
231  this.resolution = resolution;
232  }
233 
235  return status;
236  }
237 
238  public Date getSolvedDate() {
239  return solvedDate;
240  }
241 
242  public void setSolvedDate(Date solvedDate) {
243  this.solvedDate = solvedDate;
244  }
245 
246  public void setStatus(IssueStatus status) {
247  setStatus(status, null);
248  }
249 
250  public void setStatus(IssueStatus status, Date endDate) {
251  if(this.status != null && !this.status.isFinished() && status.isFinished()) {
252  solvedDate = endDate != null ? endDate : new Date();
253  } else if(!status.isFinished()) {
254  solvedDate = null;
255  }
256  this.status = status;
257  }
258 
259  public String getGrouping() {
260  return grouping;
261  }
262 
263  public void setGrouping(String grouping) {
264  this.grouping = grouping;
265  }
266 
267  public Set<IssuePredecessor> getSources() {
268  Dao dao = new DossierPU();
269  if(id != null && id > 0 && dao.isNotLoaded(sources)) {
270  sources = dao.lazyLoader(Issue.class, this, "sources").sources;
271  }
272  return sources;
273  }
274 
275  public void setSources(Set<IssuePredecessor> sources) {
276  this.sources = sources;
277  }
278 
279  public Set<IssuePredecessor> getTargets() {
280  Dao dao = new DossierPU();
281  if(id != null && id > 0 && dao.isNotLoaded(targets)) {
282  targets = dao.lazyLoader(Issue.class, this, "targets").targets;
283  }
284  return targets;
285  }
286 
287  public void setTargets(Set<IssuePredecessor> targets) {
288  this.targets = targets;
289  }
290 
291  public IssueType getType() {
292  return type;
293  }
294 
295  public void setType(IssueType type) {
296  this.type = type;
297  }
298 
299  public double getExpenses() {
300  return expenses;
301  }
302 
303  public void setExpenses(double expenses) {
304  this.expenses = expenses;
305  }
306 
307  public double getHours() {
308  return hours;
309  }
310 
311  public void setHours(double hours) {
312  this.hours = hours;
313  }
314 
315  public double getPrice() {
316  return price;
317  }
318 
319  public void setPrice(double price) {
320  this.price = price;
321  }
322 
323  public Date getStartDate() {
324  return startDate;
325  }
326 
327  public void setStartDate(Date startDate) {
328  this.startDate = startDate;
329  }
330 
332  return version;
333  }
334 
335  public void setVersion(DossierVersion version) {
336  this.version = version;
337  }
338 
339  /* IDaoEntity */
340 
341  @Override
342  public Object entityId() {
343  return id;
344  }
345 
346  @Override
347  public boolean isEmpty() {
348  return Strings.isBlank(description) || dossier == null;
349  }
350 
351 
352  /* Helpers */
353 
354  public String getFullDescription() {
355  return getFullDescription(true);
356  }
357 
358  public String getFullDescription(boolean withSubject) {
359  return description +
360  (dossier != null ? Chars.forward().spaced() + dossier.getFullDescription(withSubject) : "");
361  }
362 
363  public boolean isKnowledgeBase() {
364  if(dossier != null) {
365  return dossier.isKnowledgeBase();
366  }
367  return false;
368  }
369 
371  IssueComment last = null;
372  for(IssueComment comment : getComments()) {
373  if(last == null || last.getModification().before(comment.getModification())) {
374  last = comment;
375  }
376  }
377  return last;
378  }
379 
380  /* Duplicates */
381 
382  private transient Collection<Issue> _duplicates;
383 
384  public Collection<Issue> getDuplicates() {
385  if(_duplicates == null) {
386  Dao dao = new DossierPU();
387  _duplicates = dao.getResultList(
388  "select distinct i from Issue i " +
389  "where i.duplicated = ?",
390  new Object[] { this });
391  }
392  return _duplicates;
393  }
394 
395  /* Reporter helper */
396 
397  public IssueParticipant addReporter(Object contact) {
398  IContact c = Contacts.getEmpty();
399  c.setContact(contact);
401  }
402 
403  public IssueParticipant addResponsible(Object contact) {
404  IContact c = Contacts.getEmpty();
405  c.setContact(contact);
407  }
408 
409  public IssueParticipant addQA(Object contact) {
410  IContact c = Contacts.getEmpty();
411  c.setContact(contact);
413  }
414 
417  }
418 
421  }
422 
423  public IssueParticipant addQA(IContact contact) {
425  }
426 
428  if(id == null) return null;
429  return addParticipant(Contacts.getContactById(id), role);
430  }
431 
433  if(contact == null) return null;
435  ip.setIssue(this);
436  ip.setIdContact(contact.getId());
437  ip.setName(contact.getName());
438  ip.setRole(role);
439  getParticipants().add(ip);
440  return ip;
441  }
442 
444  if(contact == null) return null;
445  Iterator<IssueParticipant> it = getParticipants().iterator();
446  while(it.hasNext()) {
447  IssueParticipant ip = it.next();
448  if(ip.getIdContact().equals(contact.getId()) &&
449  ip.getRole().equals(role)) {
450  it.remove();
451  return ip;
452  }
453  }
454  return null;
455  }
456 
457  /* Participants helper */
458 
459  private transient ParticipantSet _participants;
460 
462  if(_participants == null) {
463  _participants = new ParticipantSet(getParticipants());
464  if(dossier != null) _participants.addAll(dossier.getFullParticipants());
465  }
466  return _participants;
467  }
468 
470  return new ParticipantSet<>(getParticipants());
471  }
472 
473  /* Data helper */
474 
475  public boolean hasData() {
476  return expenses != 0 || hours != 0 || price != 0;
477  }
478 
479  public double getSumExpenses() {
480  double r = 0;
481  for(IssueComment c : getComments()) {
482  r += c.getExpenses();
483  }
484  return r;
485  }
486 
487  public double getSumHours() {
488  double r = 0;
489  for(IssueComment c : getComments()) {
490  r += c.getHours();
491  }
492  return r;
493  }
494 
495  public double getSumPrice() {
496  double r = 0;
497  for(IssueComment c : getComments()) {
498  r += c.getPrice();
499  }
500  return r;
501  }
502 
503  public static Issue getDefaultIssue() {
504  Issue i = new Issue();
509  i.setIssueDate(new java.util.Date());
510  i.setPublishable(true);
511  return i;
512  }
513 
514  public String getIssueURL() {
515  String issueUrl;
516  if(getStatus().isFinished()) {
517  issueUrl = (String) getDossier().getFieldMap()
518  .getOrDefault("issueClosedUrl", "/app/frame?" + Interceptors.parameters("issue", getId().toString()).encoded());
519  } else {
520  issueUrl = (String) getDossier().getFieldMap()
521  .getOrDefault("issueOpenUrl", "/app/frame?" + Interceptors.parameters("issue", getId().toString()).encoded());
522  }
523  issueUrl = issueUrl.replaceAll("\\#DID", getDossier().getId() + "")
524  .replaceAll("\\#IID", getId() + "");
525  return issueUrl;
526  }
527 
528  /* Predecessors */
529 
530  private transient Collection<Issue> _body, _next;
531 
532  public Collection<Issue> getBodyIssues() {
533  if(_body == null) {
534  Dao dao = new DossierPU();
535  _body = dao.getResultList(
536  "select distinct ip.target from IssuePredecessor ip " +
537  "where ip.source = ? " +
538  "and ip.type = ? ",
539  new Object[] { this, IssuePredecessorType.START_WHEN_STARTS });
540  }
541  return _body;
542  }
543 
544  public double getBodyPercentDone() {
545  getBodyIssues();
546  if(!_body.isEmpty()) {
547  double d = 0.0;
548  for(Issue i : _body) {
549  if(i.getStatus().isFinished()) {
550  d += 1.0;
551  }
552  }
553  return new Round(d / ((double) _body.size()) * 100.0).decimals(2).value();
554  }
555  return 0.0;
556  }
557 
558  public Collection<Issue> getNextIssues() {
559  if(_next == null) {
560  Dao dao = new DossierPU();
561  _next = dao.getResultList(
562  "select distinct ip.target from IssuePredecessor ip " +
563  "where ip.source = ? " +
564  "and ip.target.grouping = ip.source.grouping " +
565  "and ip.type = ? ",
566  new Object[] { this, IssuePredecessorType.START_WHEN_ENDS });
567  }
568  return _next;
569  }
570 
571  /* XML Serializer */
572 
573  public MappingSet getSerializerMappings() {
574  MappingSet set = new MappingSet();
575  set.addMapping(Dossier.class, 1,
576  new String[] { "id" },
577  null);
578  return set;
579  }
580 
581 }
static IContact getEmpty()
Definition: Contacts.java:56
static IContact getContactById(String id)
Definition: Contacts.java:72
static Parameters parameters(String root)
Map< String, Object > getFieldMap()
Definition: Dossier.java:458
void setRole(IssueParticipantRole role)
void setPublishable(boolean publishable)
Definition: Issue.java:218
void setTargets(Set< IssuePredecessor > targets)
Definition: Issue.java:287
ParticipantSet getFullParticipants()
Definition: Issue.java:461
void setMilestone(boolean milestone)
Definition: Issue.java:194
void setType(IssueType type)
Definition: Issue.java:295
IssueResolution getResolution()
Definition: Issue.java:226
Collection< Issue > getBodyIssues()
Definition: Issue.java:532
IssuePriority getPriority()
Definition: Issue.java:206
IssueParticipant removeParticipant(IContact contact, IssueParticipantRole role)
Definition: Issue.java:443
void setResolution(IssueResolution resolution)
Definition: Issue.java:230
void setParticipants(Set< IssueParticipant > participants)
Definition: Issue.java:118
void setDescription(String description)
Definition: Issue.java:154
static Issue getDefaultIssue()
Definition: Issue.java:503
void setModification(Date modification)
Definition: Issue.java:202
Collection< Issue > getDuplicates()
Definition: Issue.java:384
IssueParticipant addReporter(Object contact)
Definition: Issue.java:397
void setVersion(DossierVersion version)
Definition: Issue.java:335
Collection< Issue > getNextIssues()
Definition: Issue.java:558
IssueParticipant addQA(Object contact)
Definition: Issue.java:409
void setStatus(IssueStatus status)
Definition: Issue.java:246
ParticipantSet< IssueParticipant > getIssueParticipants()
Definition: Issue.java:469
void setDossier(Dossier dossier)
Definition: Issue.java:162
void setControlDate(Date controlDate)
Definition: Issue.java:138
void setStatus(IssueStatus status, Date endDate)
Definition: Issue.java:250
IssueParticipant addReporter(IContact contact)
Definition: Issue.java:415
void setComments(Set< IssueComment > comments)
Definition: Issue.java:130
IssueParticipant addResponsible(IContact contact)
Definition: Issue.java:419
IssueParticipant addParticipant(IContact contact, IssueParticipantRole role)
Definition: Issue.java:432
void setPriority(IssuePriority priority)
Definition: Issue.java:210
Set< IssueComment > getComments()
Definition: Issue.java:122
void setSolvedDate(Date solvedDate)
Definition: Issue.java:242
void setDuplicated(Issue duplicated)
Definition: Issue.java:170
void setStartDate(Date startDate)
Definition: Issue.java:327
void setSources(Set< IssuePredecessor > sources)
Definition: Issue.java:275
void setHours(double hours)
Definition: Issue.java:311
IssueParticipant addQA(IContact contact)
Definition: Issue.java:423
IssueParticipant addResponsible(Object contact)
Definition: Issue.java:403
DossierVersion getVersion()
Definition: Issue.java:331
void setGrouping(String grouping)
Definition: Issue.java:263
Set< IssueParticipant > getParticipants()
Definition: Issue.java:114
IssueParticipant addParticipant(String id, IssueParticipantRole role)
Definition: Issue.java:427
Set< IssuePredecessor > getSources()
Definition: Issue.java:267
void setIssueDate(Date issueDate)
Definition: Issue.java:186
void setPrice(double price)
Definition: Issue.java:319
MappingSet getSerializerMappings()
Definition: Issue.java:573
String getFullDescription(boolean withSubject)
Definition: Issue.java:358
void setExpenses(double expenses)
Definition: Issue.java:303
IssueComment getLastComment()
Definition: Issue.java:370
Set< IssuePredecessor > getTargets()
Definition: Issue.java:279
void setDelivery(Date delivery)
Definition: Issue.java:146
boolean isNotLoaded(Object o, String attribute)
Definition: Dao.java:216
Round decimals(int digits)
Definition: Round.java:32
void setContact(Object contact)