github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/recufiscal/recu.go (about)

     1  package recufiscal
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os/exec"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	dm "github.com/benoitkugler/goACVE/server/core/datamodel"
    12  
    13  	"golang.org/x/text/encoding/unicode"
    14  
    15  	"github.com/benoitkugler/num2words"
    16  
    17  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
    18  )
    19  
    20  const (
    21  	ModelePath    = "ressources/ModeleRecuFiscalEditable.pdf"
    22  	valeurChecked = "Oui"
    23  
    24  	fdfHeader = `%FDF-1.2
    25  %,,oe"
    26  1 0 obj
    27  <<
    28  /FDF << /Fields [`
    29  
    30  	fdfFooter = `]
    31  >>
    32  >>
    33  endobj
    34  trailer
    35  <<
    36  /Root 1 0 R
    37  >>
    38  %%EOF`
    39  )
    40  
    41  var (
    42  	// Indique l'identifiant des champs présents dans le modèle.
    43  	// A synchroniser avec `MODELE_PATH`
    44  	correspondancesChamps = struct {
    45  		numero                          string
    46  		infos                           infos
    47  		categorie                       string
    48  		utilitePublique                 utilitePublique
    49  		donateur                        donateur
    50  		don                             don
    51  		articleLoi, formeDon, natureDon map[string]string
    52  		modeDon                         map[rd.ModePaiment]string
    53  		date                            date
    54  	}{
    55  		numero: "z1",
    56  		infos: infos{
    57  			nom:        "z2",
    58  			adresse:    "z4",
    59  			codePostal: "z5",
    60  			ville:      "z5b",
    61  			objectifL1: "z6",
    62  			objectifL2: "z7",
    63  		},
    64  		categorie: "z9",
    65  		utilitePublique: utilitePublique{
    66  			jourDecret:   "d1",
    67  			moisDecret:   "d2",
    68  			anneeDecret:  "d3",
    69  			jourJournal:  "d1b",
    70  			moisJournal:  "d2b",
    71  			anneeJournal: "d3b",
    72  		},
    73  		donateur: donateur{
    74  			nom:        "z29",
    75  			prenom:     "z30",
    76  			adresse:    "z31",
    77  			codePostal: "z32",
    78  			ville:      "z33",
    79  		},
    80  		don: don{
    81  			montantChiffre: "z34",
    82  			montantLettre:  "z35",
    83  			jourVersement:  "z36",
    84  			moisVersement:  "z37",
    85  			anneeVersement: "z38",
    86  		},
    87  		articleLoi: map[string]string{
    88  			"200": "z39",
    89  			"238": "z40",
    90  			"885": "z41",
    91  		},
    92  		formeDon: map[string]string{
    93  			"authentique": "z42",
    94  			"seing":       "z43",
    95  			"manuel":      "z44",
    96  			"autres":      "z45",
    97  		},
    98  		natureDon: map[string]string{
    99  			"numeraire": "z46",
   100  			"titres":    "z47",
   101  			"autres":    "z48",
   102  		},
   103  		modeDon: map[rd.ModePaiment]string{
   104  			"esp":    "z49",
   105  			"cheque": "z50",
   106  			"vir":    "z51",
   107  			"cb":     "z51",
   108  			"ancv":   "z50",
   109  		},
   110  		date: date{
   111  			jour:  "z52",
   112  			mois:  "z53",
   113  			annee: "z54",
   114  		},
   115  	}
   116  )
   117  
   118  // Définit les valeurs propres à l'ACVE
   119  const (
   120  	ArticleLoi = "200"
   121  	FormeDon   = "manuel"
   122  	NatureDon  = "numeraire"
   123  )
   124  
   125  var (
   126  	Infos = infos{
   127  		nom:        "ACVE",
   128  		adresse:    "La Maison du Rocher",
   129  		codePostal: "26160",
   130  		ville:      "CHAMALOC",
   131  		objectifL1: "Créer et gérer des séjours pour enfants, adolescents et adultes.",
   132  		objectifL2: "Faire connaître, à travers des animations adaptées à l’âge des participants, les valeurs chrétiennes.",
   133  	}
   134  	UtilitePublique = utilitePublique{
   135  		jourDecret:   "5",
   136  		moisDecret:   "1",
   137  		anneeDecret:  "1957",
   138  		jourJournal:  "29",
   139  		moisJournal:  "1",
   140  		anneeJournal: "1957",
   141  	}
   142  )
   143  
   144  type champPdf struct {
   145  	id, valeur string
   146  }
   147  
   148  type utilitePublique struct {
   149  	jourDecret   string
   150  	moisDecret   string
   151  	anneeDecret  string
   152  	jourJournal  string
   153  	moisJournal  string
   154  	anneeJournal string
   155  }
   156  
   157  type infos struct {
   158  	nom        string
   159  	adresse    string
   160  	codePostal string
   161  	ville      string
   162  	objectifL1 string
   163  	objectifL2 string
   164  }
   165  
   166  type donateur struct {
   167  	nom        string
   168  	prenom     string
   169  	adresse    string
   170  	codePostal string
   171  	ville      string
   172  }
   173  
   174  type don struct {
   175  	montantChiffre string
   176  	montantLettre  string
   177  	jourVersement  string
   178  	moisVersement  string
   179  	anneeVersement string
   180  }
   181  
   182  type date struct {
   183  	jour  string
   184  	mois  string
   185  	annee string
   186  }
   187  
   188  func champsACVE() []champPdf {
   189  	champsInfos := correspondancesChamps.infos
   190  	champUP := correspondancesChamps.utilitePublique
   191  	return []champPdf{
   192  		// ACVE
   193  		{id: champsInfos.nom, valeur: Infos.nom},
   194  		{id: champsInfos.adresse, valeur: Infos.adresse},
   195  		{id: champsInfos.codePostal, valeur: Infos.codePostal},
   196  		{id: champsInfos.ville, valeur: Infos.ville},
   197  		{id: champsInfos.objectifL1, valeur: Infos.objectifL1},
   198  		{id: champsInfos.objectifL2, valeur: Infos.objectifL2},
   199  
   200  		// CATEGORIE ASSO
   201  		{id: correspondancesChamps.categorie, valeur: valeurChecked},
   202  
   203  		{id: champUP.anneeDecret, valeur: UtilitePublique.anneeDecret},
   204  		{id: champUP.anneeJournal, valeur: UtilitePublique.anneeJournal},
   205  		{id: champUP.jourDecret, valeur: UtilitePublique.jourDecret},
   206  		{id: champUP.jourJournal, valeur: UtilitePublique.jourJournal},
   207  		{id: champUP.moisDecret, valeur: UtilitePublique.moisDecret},
   208  		{id: champUP.moisJournal, valeur: UtilitePublique.moisJournal},
   209  	}
   210  }
   211  
   212  func champsDonateur(donateur donateur) []champPdf {
   213  	champsDonateur := correspondancesChamps.donateur
   214  	return []champPdf{
   215  		{id: champsDonateur.nom, valeur: donateur.nom},
   216  		{id: champsDonateur.prenom, valeur: donateur.prenom},
   217  		{id: champsDonateur.adresse, valeur: donateur.adresse},
   218  		{id: champsDonateur.codePostal, valeur: donateur.codePostal},
   219  		{id: champsDonateur.ville, valeur: donateur.ville},
   220  	}
   221  }
   222  
   223  // `don` représente une aggrégation de plusieurs dons.
   224  func champsDon(don dm.RecuFiscal) []champPdf {
   225  	champsDon := correspondancesChamps.don
   226  	date := don.Date
   227  
   228  	montantLettre := num2words.EurosToWords(float64(don.Montant))
   229  	return []champPdf{
   230  		{id: champsDon.montantChiffre, valeur: fmt.Sprintf("%.2f", don.Montant)},
   231  		{id: champsDon.montantLettre, valeur: montantLettre},
   232  		{id: champsDon.jourVersement, valeur: strconv.Itoa(date.Day())},
   233  		{id: champsDon.moisVersement, valeur: strconv.Itoa(int(date.Month()))},
   234  		{id: champsDon.anneeVersement, valeur: strconv.Itoa(date.Year())},
   235  		{id: correspondancesChamps.modeDon[don.Mode], valeur: valeurChecked},
   236  	}
   237  }
   238  
   239  func champsTypeDon() []champPdf {
   240  	return []champPdf{
   241  		{id: correspondancesChamps.articleLoi[ArticleLoi], valeur: valeurChecked},
   242  		{id: correspondancesChamps.natureDon[NatureDon], valeur: valeurChecked},
   243  		{id: correspondancesChamps.formeDon[FormeDon], valeur: valeurChecked},
   244  	}
   245  }
   246  
   247  func champsDateEdition() []champPdf {
   248  	corresDate := correspondancesChamps.date
   249  	today := time.Now()
   250  	return []champPdf{
   251  		{id: corresDate.jour, valeur: strconv.Itoa(today.Day())},
   252  		{id: corresDate.mois, valeur: strconv.Itoa(int(today.Month()))},
   253  		{id: corresDate.annee, valeur: strconv.Itoa(today.Year())},
   254  	}
   255  }
   256  
   257  func forgeFDF(form []champPdf) string {
   258  	w := new(strings.Builder)
   259  
   260  	// Write the fdf header.
   261  	if _, err := fmt.Fprintln(w, fdfHeader); err != nil {
   262  		return ""
   263  	}
   264  
   265  	encoder := unicode.UTF16(unicode.BigEndian, unicode.UseBOM).NewEncoder()
   266  
   267  	// Write the form data.
   268  	for _, champ := range form {
   269  		utf16, _ := encoder.String(champ.valeur)
   270  		if _, err := fmt.Fprintf(w, "<< /T (%s) /V (%v)>>\n", champ.id, utf16); err != nil {
   271  			return ""
   272  		}
   273  	}
   274  
   275  	// Write the fdf footer.
   276  	if _, err := fmt.Fprintln(w, fdfFooter); err != nil {
   277  		return ""
   278  	}
   279  
   280  	return w.String()
   281  }
   282  
   283  // ParamsRecuFiscal encode les informations nécessaire à l'émission d'un reçu fiscal.
   284  // Dans le cas de plusieurs dons, `Montant` est le total des dons, et `Mode` est
   285  // le mode commun.
   286  type RecuFiscal struct {
   287  	dm.RecuFiscal
   288  	Donateur rd.Personne `json:"donateur,omitempty"`
   289  }
   290  
   291  // renvoie le numéro de reçu (unique par donateur / année)
   292  func (r RecuFiscal) numero() string {
   293  	n := time.Now()
   294  	return fmt.Sprintf("%04d%02d", r.Donateur.Id, n.Year()%100)
   295  }
   296  
   297  // RempliPdf Crée un recu, à l'aide de pdftk, qui doit être installé.
   298  func RempliPdf(params RecuFiscal) ([]byte, error) {
   299  	champNum := champPdf{id: correspondancesChamps.numero, valeur: params.numero()}
   300  	donateur := donateur{
   301  		nom:        string(params.Donateur.Nom),
   302  		prenom:     string(params.Donateur.Prenom),
   303  		adresse:    string(params.Donateur.Adresse),
   304  		codePostal: string(params.Donateur.CodePostal),
   305  		ville:      string(params.Donateur.Ville),
   306  	}
   307  	fields := []champPdf{champNum}
   308  	fields = append(fields, champsACVE()...)
   309  	fields = append(fields, champsDonateur(donateur)...)
   310  	fields = append(fields, champsDon(params.RecuFiscal)...)
   311  	fields = append(fields, champsTypeDon()...)
   312  	fields = append(fields, champsDateEdition()...)
   313  	fdf := forgeFDF(fields)
   314  	cmd := exec.Command("pdftk", ModelePath, "fill_form", "-", "output", "-")
   315  	cmd.Stdin = strings.NewReader(fdf)
   316  	errOut := new(bytes.Buffer)
   317  	cmd.Stderr = errOut
   318  	pdfBytes, err := cmd.Output()
   319  	if err != nil {
   320  		return nil, fmt.Errorf("Création du reçu impossible : %s : %s", err, errOut.String())
   321  	}
   322  	return pdfBytes, nil
   323  }