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() {}