github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/client/GUI/onglets/suivi_dossiers_fil.go (about)

     1  package onglets
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"path/filepath"
     7  
     8  	"github.com/benoitkugler/goACVE/server/core/utils/importmail"
     9  
    10  	"github.com/benoitkugler/goACVE/client/GUI/basic"
    11  	"github.com/benoitkugler/goACVE/client/GUI/fields"
    12  	"github.com/benoitkugler/goACVE/client/GUI/messages"
    13  	"github.com/benoitkugler/goACVE/client/controllers"
    14  	dm "github.com/benoitkugler/goACVE/server/core/datamodel"
    15  	"github.com/benoitkugler/goACVE/server/core/documents"
    16  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
    17  	"github.com/therecipe/qt/core"
    18  	"github.com/therecipe/qt/widgets"
    19  )
    20  
    21  type fil struct {
    22  	*widgets.QFrame
    23  
    24  	msgList     *messages.List
    25  	fold        *widgets.QAction
    26  	showDeleted *widgets.QAction
    27  
    28  	btnActions   *widgets.QPushButton
    29  	btnDocuments *widgets.QPushButton
    30  
    31  	ct *controllers.SuiviDossiers
    32  }
    33  
    34  func newFil(ct *controllers.SuiviDossiers) fil {
    35  	f := fil{QFrame: basic.Frame(), ct: ct}
    36  	f.SetObjectName("cadre")
    37  
    38  	f.msgList = messages.NewList(ct)
    39  
    40  	options := widgets.NewQMenu(f)
    41  	f.fold = options.AddAction("Déplier/Replier")
    42  	f.fold.SetCheckable(true)
    43  	f.fold.SetChecked(false)
    44  	f.fold.ConnectTriggered(func(checked bool) {
    45  		f.msgList.ToogleContent(!checked)
    46  	})
    47  	f.showDeleted = options.AddAction("Afficher/masquer les messages supprimés")
    48  	f.showDeleted.SetCheckable(true)
    49  	f.showDeleted.SetChecked(true)
    50  	f.showDeleted.ConnectTriggered(func(checked bool) {
    51  		f.msgList.ShowDeleted(checked)
    52  	})
    53  
    54  	f.SetContextMenuPolicy(core.Qt__CustomContextMenu)
    55  	f.ConnectCustomContextMenuRequested(func(pos *core.QPoint) {
    56  		options.Exec2(f.QWidget_PTR().MapToGlobal(pos), nil)
    57  	})
    58  
    59  	f.btnActions = basic.Button("Actions...")
    60  	f.btnActions.SetObjectName(basic.ONAction)
    61  	actions := widgets.NewQMenu(nil)
    62  	f.setupActions(actions)
    63  	f.btnActions.SetMenu(actions)
    64  
    65  	f.btnDocuments = basic.Button("Documents...")
    66  	actionsDocs := widgets.NewQMenu(nil)
    67  	f.setupActionsDocs(actionsDocs)
    68  	f.btnDocuments.SetMenu(actionsDocs)
    69  
    70  	importMail := basic.Button("Importer un message...")
    71  	importMail.SetIcon(basic.Icons.Mails)
    72  	importMail.SetToolTip("Ajoute un message du responsable dans le fil de discussion.")
    73  	importMail.ConnectClicked(func(_ bool) { f.importMail() })
    74  
    75  	layoutMsg := widgets.NewQGridLayout(f)
    76  	layoutMsg.AddWidget2(basic.Label("Fil de discussion"), 0, 0, 0)
    77  	layoutMsg.AddWidget2(importMail, 0, 1, 0)
    78  	layoutMsg.AddWidget2(f.btnActions, 0, 2, 0)
    79  	layoutMsg.AddWidget2(f.btnDocuments, 0, 3, 0)
    80  	layoutMsg.AddWidget3(f.msgList, 1, 0, 1, 4, 0)
    81  	layoutMsg.SetColumnStretch(0, 1)
    82  	layoutMsg.SetRowStretch(1, 1)
    83  	return f
    84  }
    85  
    86  func (f fil) setupActions(menu *widgets.QMenu) {
    87  	menu.AddAction("Nouveau message...").ConnectTriggered(func(_ bool) { f.nouveauMessage() })
    88  	menu.AddSeparator()
    89  	menu.AddAction("Envoyer un accusé de réception").ConnectTriggered(func(_ bool) { f.envoiAccuse() })
    90  	menu.AddAction("Déclarer un accompte...").ConnectTriggered(func(_ bool) { f.declareAccompte() })
    91  	menu.AddAction("Envoyer la facture").ConnectTriggered(func(_ bool) { f.envoiFacture() })
    92  	menu.AddAction("Déclarer un paiement...").ConnectTriggered(func(_ bool) { f.declarePaiement() })
    93  	menu.AddSeparator()
    94  	menu.AddAction("Envoyer les documents...").ConnectTriggered(func(_ bool) { f.envoiDocuments() })
    95  	menu.AddSeparator()
    96  	menu.AddAction("Envoyer une facture acquittée").ConnectTriggered(func(_ bool) { f.envoiFactureAc() })
    97  	menu.AddAction("Envoyer une attestation de présence").ConnectTriggered(func(_ bool) { f.envoiAttestationPres() })
    98  }
    99  
   100  func (f fil) setupActionsDocs(menu *widgets.QMenu) {
   101  	menu.AddAction("RIB").ConnectTriggered(func(_ bool) { f.exportRIB() })
   102  	menu.AddSeparator()
   103  	menu.AddAction("Facture").ConnectTriggered(func(_ bool) { f.exportFacture() })
   104  	menu.AddAction("Attestation de présence").ConnectTriggered(func(_ bool) { f.exportAttestionPresence() })
   105  }
   106  
   107  func (f fil) setMessages(messages []dm.PseudoMessage) {
   108  	cache := f.ct.NewCacheGroupes()
   109  	f.msgList.SetData(cache.PromoteMessages(messages))
   110  	f.msgList.ToogleContent(!f.fold.IsChecked())
   111  	f.msgList.ShowDeleted(f.showDeleted.IsChecked())
   112  }
   113  
   114  func (f fil) enableActions(actif bool) {
   115  	f.btnActions.SetEnabled(actif)
   116  	f.btnDocuments.SetEnabled(actif)
   117  }
   118  
   119  func (f fil) facture() (dm.AccesFacture, bool) {
   120  	id := f.ct.Etat.FactureCurrent
   121  	if id == nil {
   122  		return dm.AccesFacture{}, false
   123  	}
   124  	return f.ct.Base.NewFacture(id.Int64()), true
   125  }
   126  
   127  func (f fil) nouveauMessage() {
   128  	fac, ok := f.facture()
   129  	if !ok {
   130  		return
   131  	}
   132  	contenu, ok := messages.EditMessage("", true, 0)
   133  	if !ok {
   134  		return
   135  	}
   136  	f.ct.EnvoiMessageCentre(fac, contenu)
   137  }
   138  func (f fil) envoiAccuse() {
   139  	fac, ok := f.facture()
   140  	if !ok {
   141  		return
   142  	}
   143  	f.ct.EnvoiAccuseReception(fac)
   144  
   145  }
   146  func (f fil) declareAccompte() {
   147  	f.createPaiement(true)
   148  }
   149  func (f fil) envoiFacture() {
   150  	fac, ok := f.facture()
   151  	if !ok {
   152  		return
   153  	}
   154  	if fac.EtatFinancier(dm.CacheEtatFinancier{}, false).IsAcquitte() {
   155  		b := basic.Confirmation("Le paiement pour ce dossier est complet. Ne voulez-vous pas plutôt envoyer une <b>facture acquittée</b> ?", "Envoyer quand même", basic.ONAction)
   156  		if !b {
   157  			return
   158  		}
   159  	}
   160  	f.ct.EnvoiFacture(fac)
   161  }
   162  
   163  func (f fil) declarePaiement() {
   164  	f.createPaiement(false)
   165  }
   166  
   167  func (f fil) envoiDocuments() {
   168  	fac, ok := f.facture()
   169  	if !ok {
   170  		return
   171  	}
   172  
   173  	var camps []dm.AccesCamp
   174  	idsCamps, _ := fac.Camps(nil)
   175  	for idCamp := range idsCamps {
   176  		camps = append(camps, fac.Base.NewCamp(idCamp))
   177  	}
   178  
   179  	d := basic.Dialog2("Choix du séjour")
   180  	choixCamp := fields.NewCamp(true, fac.Base, false)
   181  	choixCamp.SetChoices(camps)
   182  
   183  	valid := basic.Button("Envoyer")
   184  	valid.SetEnabled(false)
   185  	choixCamp.ConnectCurrentIndexChanged(func(_ int) {
   186  		valid.SetEnabled(choixCamp.GetData() != nil)
   187  	})
   188  
   189  	if len(camps) > 0 { // choix par défaut
   190  		choixCamp.SetData(rd.NewOptionnalId(camps[0].Id))
   191  	}
   192  
   193  	valid.SetObjectName(basic.ONAction)
   194  	valid.ConnectClicked(func(_ bool) {
   195  		d.Accept()
   196  	})
   197  
   198  	layout := widgets.NewQFormLayout(d)
   199  	layout.AddRow3("Séjour", choixCamp)
   200  	layout.AddRow5(valid)
   201  
   202  	if d.Exec() == 0 {
   203  		f.ct.Main().ShowStandard("Envoi annulé.", false)
   204  		return
   205  	}
   206  
   207  	streamEnvoiDocuments(f.ct, choixCamp.GetData().Id, rd.Ids{fac.Id})
   208  }
   209  
   210  func (f fil) envoiFactureAc() {
   211  	fac, ok := f.facture()
   212  	if !ok {
   213  		return
   214  	}
   215  	f.ct.EnvoiFactureAcquittee(fac)
   216  }
   217  func (f fil) envoiAttestationPres() {
   218  	fac, ok := f.facture()
   219  	if !ok {
   220  		basic.ShowError(errors.New("Aucun dossier n'est sélectionné."))
   221  		return
   222  	}
   223  	f.ct.EnvoiAttestationPresence(fac)
   224  }
   225  
   226  // ------------- documents ------------------------------
   227  
   228  func (f fil) exportRIB() {
   229  	path, err := filepath.Abs(documents.PathRIB)
   230  	if err != nil {
   231  		basic.ShowError(err)
   232  		return
   233  	}
   234  	basic.ShowFile(path)
   235  }
   236  
   237  func choixDestinataire(fac dm.AccesFacture) (rd.Destinataire, bool) {
   238  	ds := fac.ChoixDestinataires()
   239  	if len(ds) == 1 {
   240  		return ds[0], true
   241  	}
   242  	// on propose le choix du destinataire
   243  
   244  	d := basic.Dialog2("Choix du destinataire")
   245  
   246  	choix := widgets.NewQComboBox(nil)
   247  	for i, dest := range ds {
   248  		label := fmt.Sprintf("%s (%s)", dest.NomPrenom, dest.Ville)
   249  		if i == 0 {
   250  			label += " - Responsable"
   251  		}
   252  		choix.AddItem(label, core.NewQVariant())
   253  	}
   254  	choix.SetCurrentIndex(0)
   255  
   256  	valid := basic.Button("Valider")
   257  	valid.ConnectClicked(func(_ bool) {
   258  		d.Accept()
   259  	})
   260  
   261  	lay := widgets.NewQFormLayout(d)
   262  	lay.SetVerticalSpacing(10)
   263  	lay.AddRow5(basic.Label("Vous pouvez choisir un <b>destinataire</b> différent du responsable pour l'entête du document :"))
   264  	lay.AddRow3("Destinataire", choix)
   265  	lay.AddRow5(valid)
   266  
   267  	ok := d.Exec() > 0
   268  	out := ds[choix.CurrentIndex()]
   269  	return out, ok
   270  }
   271  
   272  func (f fil) exportFacture() {
   273  	fac, ok := f.facture()
   274  	if !ok {
   275  		return
   276  	}
   277  	dest, ok := choixDestinataire(fac)
   278  	if !ok {
   279  		return
   280  	}
   281  	meta := documents.Facture{
   282  		Destinataire: dest,
   283  		Bilan:        fac.EtatFinancier(dm.CacheEtatFinancier{}, false),
   284  	}
   285  	path := f.ct.Main().GenereExportDocument(meta)
   286  	if path != "" {
   287  		basic.ShowFile(path)
   288  	}
   289  }
   290  
   291  func (f fil) exportAttestionPresence() {
   292  	fac, ok := f.facture()
   293  	if !ok {
   294  		return
   295  	}
   296  	dest, ok := choixDestinataire(fac)
   297  	if !ok {
   298  		return
   299  	}
   300  	meta := documents.Presence{
   301  		Destinataire: dest,
   302  		// on ne sélectionne pas la liste d'attente
   303  		Participants: fac.EtatFinancier(dm.CacheEtatFinancier{}, false).Participants,
   304  	}
   305  	path := f.ct.Main().GenereExportDocument(meta)
   306  	if path != "" {
   307  		basic.ShowFile(path)
   308  	}
   309  }
   310  
   311  func (f fil) createPaiement(isAcompte bool) {
   312  	fac, ok := f.facture()
   313  	if !ok {
   314  		return
   315  	}
   316  	paie := CreePaiement(f.ct.Base, rd.NewOptionnalId(fac.Id), InitialPaiement{
   317  		IsAcompte: isAcompte,
   318  		// par défaut, le responsable, mais peut changer
   319  		LabelPayeur: fac.GetPersonne().RawData().NomPrenom().String(),
   320  	})
   321  	if paie == nil {
   322  		return
   323  	}
   324  	f.SetEnabled(false)
   325  	f.Repaint()
   326  	f.ct.AjoutePaiement(*paie)
   327  	f.SetEnabled(true)
   328  }
   329  
   330  func (f fil) importMail() {
   331  	dial := basic.Dialog2("Importer un message")
   332  
   333  	content := fields.NewMultiLineString(true, "Message...")
   334  	content.SetMinimumHeight(300)
   335  	idRespo := fields.NewBoutonIdFacture(true, f.ct.Base)
   336  	hint := basic.Label(`<i>Astuce :</i> Pour importer un email depuis <b>Thunderbird</b>, utiliser <br/>
   337  la suite de raccourci <b>Ctrl + U</b>, <b>Ctrl + A</b>, <b>Ctrl + C</b>, <b>Ctrl + W</b>, 
   338  puis cliquer sur le bouton <i>Charger depuis le presse-papier</i> ci-dessous.`)
   339  	importMa := basic.Button("Charger depuis le presse-papier")
   340  	valid := basic.Button("Valider")
   341  	valid.SetEnabled(false)
   342  
   343  	// condition de validation
   344  	setValidEnabled := func() {
   345  		isValid := content.GetData().TrimSpace() != "" && idRespo.GetData() != nil
   346  		valid.SetEnabled(isValid)
   347  	}
   348  	content.ConnectTextChanged(setValidEnabled)
   349  	idRespo.DataChanged = setValidEnabled
   350  
   351  	var imported importmail.Mail
   352  	importMa.ConnectClicked(func(_ bool) {
   353  		rawMail := basic.Paste()
   354  		imp, id, err := f.ct.ImportMail(rawMail)
   355  		if err != nil {
   356  			f.ct.Main().ShowError(err)
   357  			return
   358  		}
   359  		content.SetData(rd.String(imp.Content))
   360  		idRespo.SetData(id.AsIId())
   361  		imported = imp
   362  		setValidEnabled() // modification "interne"
   363  		f.ct.Main().ShowStandard("Mail importé", false)
   364  	})
   365  
   366  	valid.ConnectClicked(func(_ bool) {
   367  		// utilise les valeurs utilisateur
   368  		f.ct.CreateMessageResponsable(content.GetData(), idRespo.GetData().Int64(), imported)
   369  		dial.Accept()
   370  	})
   371  
   372  	// initialise avec le dossier courant, s'il existe
   373  	if fc := f.ct.Etat.FactureCurrent; fc != nil {
   374  		idRespo.SetData(fc)
   375  	}
   376  
   377  	layout := widgets.NewQFormLayout(dial)
   378  	layout.AddRow3("Dossier", idRespo)
   379  	layout.AddRow3("Contenu", content)
   380  	layout.AddRow5(hint)
   381  	rowButtons := widgets.NewQHBoxLayout()
   382  	rowButtons.AddWidget(importMa, 1, 0)
   383  	rowButtons.AddStretch(2)
   384  	rowButtons.AddWidget(valid, 1, 0)
   385  	layout.AddRow6(rowButtons)
   386  	dial.Exec()
   387  }