github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/core/rawdata/composites/finances.go (about)

     1  package composites
     2  
     3  import (
     4  	"fmt"
     5  
     6  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
     7  )
     8  
     9  // ValeurEffective renvoie :
    10  // 	- le montant dans le cas d'une aide absolue
    11  // 	- le montant fois le nombre de jours (en prenant en compte une éventuelle limite) sinon
    12  // Si `withRemise` vaut true les remises (%) sont appliquées.
    13  func (a AideParticipantCamp) ValeurEffective(withRemise bool) rd.Euros {
    14  	val := a.Aide.Valeur
    15  	nbJours := a.ParticipantCamp.AsOptionParticipantCamp().NbJours()
    16  	if a.Aide.ParJour {
    17  		limite := a.Aide.NbJoursMax
    18  		if limite > 0 && limite < nbJours {
    19  			nbJours = limite
    20  		}
    21  		val = val * rd.Euros(nbJours)
    22  	}
    23  	if withRemise {
    24  		val = val.Remise(a.Participant.Remises.Pourcent())
    25  	}
    26  	return val
    27  }
    28  
    29  type AideComplet struct {
    30  	AideStructureaide
    31  	ParticipantCamp
    32  }
    33  
    34  func (a AideComplet) Description() string {
    35  	aide := a.ParticipantCamp.WithAide(a.Aide)
    36  	return fmt.Sprintf("%s : %s", a.Structureaide.Nom, aide.ValeurEffective(false))
    37  }
    38  
    39  // c'est celui du participant, ou sinon celui du responsable
    40  func (p ParticipantFacturePersonne) getQuotientFamilial() rd.Int {
    41  	qfPart := p.Participant.OptionPrix.QuotientFamilial
    42  	if qfPart != 0 {
    43  		return qfPart
    44  	}
    45  	if p.Participant.IdFacture.IsNil() {
    46  		return 0
    47  	}
    48  	return p.FacturePersonne.Personne.QuotientFamilial
    49  }
    50  
    51  // StatutPrix renvoi la catégorie de prix du participant
    52  // Une catégorie avec un Id vide équivaut à l'absence de catégorie
    53  func (p OptionParticipantCamp) StatutPrix() rd.PrixParStatut {
    54  	s := p.OptionPrix.Statut
    55  	if s == 0 {
    56  		return rd.PrixParStatut{}
    57  	}
    58  	campOption := p.Camp.OptionPrix
    59  	if campOption.Active != rd.OptionsPrix.STATUT {
    60  		return rd.PrixParStatut{}
    61  	}
    62  	for _, info := range campOption.Statut {
    63  		if info.Id == s {
    64  			return info
    65  		}
    66  	}
    67  	return rd.PrixParStatut{}
    68  }
    69  
    70  type ParticipantComplet struct {
    71  	ParticipantCamp
    72  	FacturePersonne
    73  }
    74  
    75  func (p ParticipantComplet) AsFacture() ParticipantFacturePersonne {
    76  	return ParticipantFacturePersonne{Participant: p.Participant, FacturePersonne: p.FacturePersonne}
    77  }
    78  
    79  func (p ParticipantCamp) WithAide(aide rd.Aide) AideParticipantCamp {
    80  	return AideParticipantCamp{Aide: aide, ParticipantCamp: p}
    81  }
    82  
    83  func getIndexQF(qf rd.Int) int {
    84  	n := len(rd.QuotientFamilial)
    85  	for i := 0; i < n-1; i++ {
    86  		q1, q2 := rd.QuotientFamilial[i], rd.QuotientFamilial[i+1]
    87  		if q1 < qf && qf <= q2 {
    88  			return i
    89  		}
    90  	}
    91  	return n - 1
    92  }
    93  
    94  // Renvoie le prix du camp, en prenant en compte une éventuelle option.
    95  // Renvoi aussi une description de l'éventuelle option.
    96  func (p ParticipantComplet) prixCampBase() (rd.Euros, string) {
    97  	desc := p.AsOptionParticipantCamp().Description().String()
    98  
    99  	quotientFamilial := p.AsFacture().getQuotientFamilial()
   100  	optionPrixParticipant := p.Participant.OptionPrix
   101  	optionPrixCamp := p.Camp.OptionPrix
   102  	optionActive := optionPrixCamp.Active
   103  	var prix rd.Euros = -1
   104  	switch optionActive {
   105  	case rd.OptionsPrix.SEMAINE:
   106  		semaine := optionPrixParticipant.Semaine
   107  		prixSem := optionPrixCamp.Semaine
   108  		if semaine == rd.SSe1 {
   109  			prix = prixSem.Prix1
   110  		} else if semaine == rd.SSe2 {
   111  			prix = prixSem.Prix2
   112  		}
   113  	case rd.OptionsPrix.STATUT:
   114  		statutInfo := p.AsOptionParticipantCamp().StatutPrix()
   115  		if statutInfo.Id != 0 {
   116  			prix = statutInfo.Prix
   117  		}
   118  	case rd.OptionsPrix.QUOTIENT_FAMILIAL:
   119  		qf := optionPrixParticipant.QuotientFamilial
   120  		if qf == 0 {
   121  			qf = quotientFamilial
   122  		}
   123  		if qf > 0 {
   124  			qfCamp := optionPrixCamp.QuotientFamilial
   125  			prix = qfCamp[getIndexQF(qf)]
   126  		}
   127  	case rd.OptionsPrix.JOUR:
   128  		nbJours := p.ParticipantCamp.AsOptionParticipantCamp().NbJours()
   129  		prixJours := optionPrixCamp.Jour
   130  		if nbJours == 0 { // invalide -> camp entier
   131  			break
   132  		}
   133  		if int(nbJours) <= len(prixJours) {
   134  			prix = prixJours[nbJours-1]
   135  		}
   136  	}
   137  	if prix == -1 {
   138  		prix = p.Camp.Prix
   139  	}
   140  	return prix, desc
   141  }
   142  
   143  type resolvedAide struct {
   144  	Description string
   145  	Montant     rd.Euros
   146  	Valide      bool
   147  }
   148  
   149  // BilanPrixCamp regroupe les infos relative
   150  // au coût d'un séjour pour un participant.
   151  type BilanPrixCamp struct {
   152  	Aides   []resolvedAide
   153  	Remises rd.Remises
   154  
   155  	// prix du camp (option comprise)
   156  	PrixBase        rd.Euros
   157  	DescriptionPrix string
   158  }
   159  
   160  // PrixSansRemises renvoie le prix du séjour si on applique les aides (extérieures)
   161  // mais pas les remises.
   162  func (b BilanPrixCamp) PrixSansRemises() rd.Euros {
   163  	p := b.PrixBase - b.TotalAides()
   164  	if p < 0 {
   165  		p = 0
   166  	}
   167  	return p
   168  }
   169  
   170  // PrixSansAide renvoie le prix du séjour en appliquant les remises,
   171  // mais pas les aides.
   172  func (b BilanPrixCamp) PrixSansAide() rd.Euros {
   173  	return b.PrixBase.Remise(b.Remises.ReducEquipiers+b.Remises.ReducEnfants) - b.Remises.ReducSpeciale
   174  }
   175  
   176  func (b BilanPrixCamp) TotalAides() rd.Euros {
   177  	var out rd.Euros
   178  	for _, aide := range b.Aides {
   179  		if aide.Valide {
   180  			out += aide.Montant
   181  		}
   182  	}
   183  	return out
   184  }
   185  
   186  func (b BilanPrixCamp) PrixNet() rd.Euros {
   187  	p := b.PrixSansRemises().Remise(b.Remises.Pourcent()) - b.Remises.ReducSpeciale
   188  	if p < 0 {
   189  		p = 0
   190  	}
   191  	return p
   192  }
   193  
   194  // EtatFinancier calcule le prix du sejour pour le participant,
   195  // en prenant en compte les options, les remises et les aides.
   196  // Si `withAidesInvalides` vaut `false`, les aides non validées ne sont pas renvoyées.
   197  // `aides` n'est pas filtrée
   198  func (p ParticipantComplet) EtatFinancier(aides []AideStructureaide, withAidesInvalides bool) BilanPrixCamp {
   199  	var out BilanPrixCamp
   200  	out.PrixBase, out.DescriptionPrix = p.prixCampBase()
   201  	out.Remises = p.Participant.Remises
   202  	for _, aide := range aides {
   203  		va := p.WithAide(aide.Aide).ValeurEffective(false)
   204  		dumped := resolvedAide{
   205  			Description: AideComplet{AideStructureaide: aide, ParticipantCamp: p.ParticipantCamp}.Description(),
   206  			Montant:     va,
   207  			Valide:      bool(aide.Aide.Valide),
   208  		}
   209  		if withAidesInvalides || dumped.Valide {
   210  			out.Aides = append(out.Aides, dumped)
   211  		}
   212  	}
   213  	return out
   214  }
   215  
   216  // ByFacture associe à chaque facture ses paiements (cout linéaire)
   217  func (ps PaiementFactures) ByFacture() map[int64]PaiementFactures {
   218  	out := map[int64]PaiementFactures{}
   219  	for _, pf := range ps {
   220  		out[pf.IdFacture] = append(out[pf.IdFacture], pf)
   221  	}
   222  	return out
   223  }