github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/core/datamodel/messages.go (about)

     1  package datamodel
     2  
     3  import (
     4  	"sort"
     5  	"strings"
     6  	"time"
     7  
     8  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
     9  )
    10  
    11  const (
    12  	POVCentre PointOfView = iota
    13  	POVResponsable
    14  )
    15  
    16  // EtatDossier stocke les informations nécessaires à l'etablissement
    17  // de l'état d'un dossier
    18  type EtatDossier struct {
    19  	messages  []AccesMessage
    20  	paiements []AccesPaiement
    21  }
    22  
    23  // GetEtat rassemble les données décrivant l'avancement du dossier
    24  // (messages, paiements, ...)
    25  func (ac AccesFacture) GetEtat(cache1 LiensFactureMessages, cache2 LiensFacturePaiements) EtatDossier {
    26  	if cache1 == nil {
    27  		cache1 = ac.Base.ResoudMessages()
    28  	}
    29  	if cache2 == nil {
    30  		cache2 = ac.Base.ResoudPaiements()
    31  	}
    32  	var out EtatDossier
    33  	for _, id := range cache1[ac.Id] {
    34  		out.messages = append(out.messages, ac.Base.NewMessage(id))
    35  	}
    36  	for _, id := range cache2[ac.Id] {
    37  		out.paiements = append(out.paiements, ac.Base.NewPaiement(id))
    38  	}
    39  	// on tri les messages par date de création
    40  	sort.Slice(out.messages, func(i, j int) bool {
    41  		return out.messages[i].RawData().Created.Time().Before(out.messages[j].RawData().Created.Time())
    42  	})
    43  	return out
    44  }
    45  
    46  func (e EtatDossier) InscriptionDateHeure() rd.Time {
    47  	for _, message := range e.messages {
    48  		if message.RawData().Kind == rd.MInscription {
    49  			return message.RawData().Created
    50  		}
    51  	}
    52  	return rd.Time{}
    53  }
    54  
    55  // LastModified renvoie le moment de la dernière action (visible)
    56  // effectuée sur le dossier
    57  func (e EtatDossier) LastModified() rd.Time {
    58  	ps := e.PseudoMessages(POVCentre)
    59  	if len(ps) == 0 {
    60  		return rd.Time{}
    61  	}
    62  	// pseudo message sont triés du plus ancien au plus récent
    63  	return ps[len(ps)-1].Created
    64  }
    65  
    66  func (e EtatDossier) IsFactureRappel(mFac PseudoMessage) bool {
    67  	for _, message := range e.messages {
    68  		isFacture := message.RawData().Kind == rd.MFacture
    69  		isBefore := message.RawData().Created.Time().Before(mFac.Created.Time())
    70  		isDifferent := message.Id != mFac.Id
    71  		if isFacture && isBefore && isDifferent {
    72  			return true
    73  		}
    74  	}
    75  	return false
    76  }
    77  
    78  // Info aggrège les messages envoyés par le responsable
    79  func (e EtatDossier) Info() string {
    80  	var outs []string
    81  	for _, mess := range e.messages {
    82  		if mess.RawData().Kind == rd.MResponsable {
    83  			contenu := mess.Base.MessageMessages[mess.Id]
    84  			outs = append(outs, contenu.Contenu.TrimSpace().String())
    85  		}
    86  	}
    87  	return strings.Join(outs, "\n\n")
    88  }
    89  
    90  // PseudoMessages expose les actions du dossier, du plus ancien au plus récent.
    91  func (e EtatDossier) PseudoMessages(pov PointOfView) []PseudoMessage {
    92  	out := make([]PseudoMessage, 0, len(e.messages)+len(e.paiements))
    93  	for _, message := range e.messages {
    94  		out = append(out, message.ToPseudo(pov))
    95  	}
    96  	for _, paiement := range e.paiements {
    97  		out = append(out, paiement.ToPseudo(pov))
    98  	}
    99  	// détérminisme même pour les des messages simulatnés
   100  	sort.Slice(out, func(i, j int) bool {
   101  		return out[i].Kind < out[j].Kind
   102  	})
   103  	sort.SliceStable(out, func(i, j int) bool {
   104  		return out[i].Created.Time().Before(out[j].Created.Time())
   105  	})
   106  	return out
   107  }
   108  
   109  type PointOfView uint8
   110  
   111  func (p PointOfView) IsViewable(kind rd.MessageKind) bool {
   112  	// côté espace perso, le marquage vu/non vu est automatique (implémenté côté serveur)
   113  	if p == POVResponsable {
   114  		return false
   115  	}
   116  	switch kind {
   117  	case rd.MResponsable:
   118  		return true
   119  	default:
   120  		return false
   121  	}
   122  }
   123  
   124  func (p PointOfView) IsEditable(kind rd.MessageKind) bool {
   125  	switch p {
   126  	case POVCentre:
   127  		return kind == rd.MCentre
   128  	case POVResponsable:
   129  		return kind == rd.MResponsable
   130  	default:
   131  		return false
   132  	}
   133  }
   134  
   135  func (p PointOfView) IsDeletable(kind rd.MessageKind) bool {
   136  	switch p {
   137  	case POVCentre:
   138  		return kind == rd.MCentre || kind == rd.MFacture || kind == rd.MDocuments || kind == rd.MFactureAcquittee || kind == rd.MAttestationPresence || kind == rd.MSondage || kind == rd.MPlaceLiberee
   139  	case POVResponsable:
   140  		return kind == rd.MResponsable
   141  	default:
   142  		return false
   143  	}
   144  }
   145  
   146  // MessageLabel précise le titre d'un message libre
   147  func (p PointOfView) MessageLabel(kind rd.MessageKind) string {
   148  	isEnvoye := (p == POVResponsable && kind == rd.MResponsable) || (p == POVCentre && kind == rd.MCentre)
   149  	if isEnvoye {
   150  		return "Message envoyé"
   151  	}
   152  	return "Message reçu"
   153  }
   154  
   155  // PseudoMessage expose un évènement (message ou paiement)
   156  type PseudoMessage struct {
   157  	Id        int64 `json:"id"` // message ou paiement
   158  	IdFacture int64 `json:"id_facture"`
   159  
   160  	Label       string      `json:"label"`
   161  	Color       rd.HexColor `json:"color"`
   162  	Created     rd.Time     `json:"created"`
   163  	Modified    rd.Time     `json:"modified"`
   164  	Vu          bool        `json:"vu"`
   165  	IsViewable  bool        `json:"is_viewable"`
   166  	IsEditable  bool        `json:"is_editable"`
   167  	IsDeletable bool        `json:"is_deletable"`
   168  
   169  	Kind    rd.MessageKind `json:"kind"`
   170  	Contenu MessageContenu `json:"contenu"`
   171  }
   172  
   173  // LastEvent renvoie la dernière modification (au sens large)
   174  func (ps PseudoMessage) LastEvent() time.Time {
   175  	if ps.Modified.Time().IsZero() {
   176  		return ps.Created.Time()
   177  	}
   178  	return ps.Modified.Time()
   179  }
   180  
   181  // ------------- implémentations ---------------------------
   182  
   183  // NewPseudoMessage permet de construire un pseudo message sans base locale.
   184  func NewPseudoMessage(message rd.Message, pov PointOfView, contenu MessageContenu) PseudoMessage {
   185  	label := message.Kind.String()
   186  	// specialize messages perso
   187  	if message.Kind == rd.MResponsable || message.Kind == rd.MCentre {
   188  		label = pov.MessageLabel(message.Kind)
   189  	}
   190  	return PseudoMessage{
   191  		Id:          message.Id,
   192  		IdFacture:   message.IdFacture,
   193  		Label:       label,
   194  		Color:       message.Kind.Color(),
   195  		Created:     message.Created,
   196  		Modified:    message.Modified,
   197  		Vu:          message.Vu,
   198  		IsViewable:  pov.IsViewable(message.Kind),
   199  		IsEditable:  pov.IsEditable(message.Kind),
   200  		IsDeletable: pov.IsDeletable(message.Kind),
   201  		Kind:        message.Kind,
   202  		Contenu:     contenu,
   203  	}
   204  }
   205  
   206  func (ac AccesMessage) ToPseudo(pov PointOfView) PseudoMessage {
   207  	return NewPseudoMessage(ac.RawData(), pov, ac.toContenu())
   208  }
   209  
   210  func (ac AccesMessage) toContenu() MessageContenu {
   211  	message := ac.RawData()
   212  	switch message.Kind {
   213  	case rd.MResponsable, rd.MCentre:
   214  		return ContenuPerso(ac.Base.MessageMessages[ac.Id].Contenu)
   215  	case rd.MDocuments:
   216  		return ContenuDocument(ac.Base.MessageDocuments[ac.Id])
   217  	case rd.MSondage:
   218  		return ContenuSondage(ac.Base.MessageSondages[ac.Id])
   219  	case rd.MPlaceLiberee:
   220  		return ContenuPlaceLiberee(ac.Base.MessagePlaceliberes[ac.Id])
   221  	case rd.MFactureAcquittee, rd.MAttestationPresence:
   222  		return ContenuAttestation{Distribution: ac.Base.MessageAttestations[ac.Id].Distribution}
   223  	}
   224  	return nil
   225  }
   226  
   227  func (ac AccesPaiement) ToPseudo(_ PointOfView) PseudoMessage {
   228  	paiement := ac.RawData()
   229  	return PseudoMessage{
   230  		Id:        paiement.Id,
   231  		IdFacture: paiement.IdFacture,
   232  		Label:     "Paiement",
   233  		Color:     rd.MPaiement.Color(),
   234  		Created:   paiement.DateReglement,
   235  		Vu:        true,
   236  		Kind:      rd.MPaiement,
   237  		Contenu:   nil,
   238  	}
   239  }
   240  
   241  // MessageContenu spécialise le contenu d'un message
   242  type MessageContenu interface {
   243  	isContenu()
   244  }
   245  
   246  // --------- implementations ----------------------
   247  
   248  type ContenuPerso string
   249  
   250  func (ContenuPerso) isContenu() {}
   251  
   252  type ContenuDocument rd.MessageDocument
   253  
   254  func (ContenuDocument) isContenu() {}
   255  
   256  type ContenuSondage rd.MessageSondage
   257  
   258  func (ContenuSondage) isContenu() {}
   259  
   260  type ContenuPlaceLiberee rd.MessagePlacelibere
   261  
   262  func (ContenuPlaceLiberee) isContenu() {}
   263  
   264  type ContenuAttestation struct {
   265  	Distribution rd.Distribution `json:"distribution"`
   266  }
   267  
   268  func (ContenuAttestation) isContenu() {}