mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-12 13:46:51 +00:00
create invoice
This commit is contained in:
parent
bfde4eb601
commit
7796f71590
9 changed files with 302 additions and 227 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|
@ -147,142 +148,143 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="header space-between">
|
<div class="header space-between">
|
||||||
<img class="logo" src="assets/img/logo.png"/>
|
<img class="logo" src="assets/img/logo.png" />
|
||||||
<div class="payee">
|
<div class="payee">
|
||||||
<b>{{ .Host.Name }}</b><br/>
|
<b>{{ .Host.Name }}</b><br />
|
||||||
{{ .Host.Address }}<br/>
|
{{ .Host.Address }}<br />
|
||||||
{{ .Host.ZipCode }} {{ .Host.City }}<br/>
|
{{ .Host.ZipCode }} {{ .Host.City }}<br />
|
||||||
<b>Tel :</b> {{ .Host.Phone }}<br/>
|
<b>Tel :</b> {{ .Host.Phone }}<br />
|
||||||
<b>Mail :</b> {{ .Host.Email }}<br/>
|
<b>Mail :</b> {{ .Host.Email }}<br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="billing-details space-between">
|
||||||
|
<table class="info-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{{ .Name }}</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Tel :</strong></td>
|
||||||
|
<td>{{ .PhoneNumber }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Client :</strong></td>
|
||||||
|
<td>{{ .CustomersNumber }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Plateforme :</strong></td>
|
||||||
|
<td>{{ .Platform }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="info-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Nº de facture :</strong></td>
|
||||||
|
<td>{{ .ID }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Du :</strong></td>
|
||||||
|
<td>{{ .From }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Au :</strong></td>
|
||||||
|
<td>{{ .To }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Montant Total :</strong></td>
|
||||||
|
<td>{{ .Total }} €</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="order-details">
|
||||||
|
<table class="items-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Objet</th>
|
||||||
|
<th>Quantité</th>
|
||||||
|
<th>Prix (€)</th>
|
||||||
|
<th class="align-right">Total (€)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-gray rounded">
|
||||||
|
{{ range .Lines }}
|
||||||
|
<tr class="item-row">
|
||||||
|
<td class="text-break product_name">{{ .Name }}</td>
|
||||||
|
<td>{{ .Quantity }}</td>
|
||||||
|
<td>{{ .Price }}</td>
|
||||||
|
<td class="align-right">{{ .Total }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<hr/>
|
|
||||||
<div class="billing-details space-between">
|
|
||||||
<table class="info-table">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td><strong>{{ .Name }}</strong></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Tel :</strong></td>
|
|
||||||
<td>{{ .PhoneNumber }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Client :</strong></td>
|
|
||||||
<td>{{ .CustomersNumber }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Plateforme :</strong></td>
|
|
||||||
<td>{{ .Platform }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<table class="info-table">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Nº de facture :</strong></td>
|
|
||||||
<td>{{ .ID }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Du :</strong></td>
|
|
||||||
<td>{{ .From }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Au :</strong></td>
|
|
||||||
<td>{{ .To }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><strong>Montant Total :</strong></td>
|
|
||||||
<td>{{ .Total }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<hr/>
|
|
||||||
<div class="order-details">
|
|
||||||
<table class="items-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Objet</th>
|
|
||||||
<th>Quantité</th>
|
|
||||||
<th>Prix (€)</th>
|
|
||||||
<th class="align-right">Total (€)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="bg-gray rounded">
|
|
||||||
{{ range .Lines }}
|
|
||||||
<tr class="item-row">
|
|
||||||
<td class="text-break product_name">{{ .Name }}</td>
|
|
||||||
<td>{{ .Quantity }}</td>
|
|
||||||
<td>{{ .Price }}</td>
|
|
||||||
<td class="align-right">{{ .Total }}</td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="payment-history">
|
<div class="payment-history">
|
||||||
<h3>Historique des Paiements</h3>
|
<h3>Historique des Paiements</h3>
|
||||||
<table class="history-table">
|
<table class="history-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Mode de Paiement</th>
|
<th>Mode de Paiement</th>
|
||||||
<th>Montant (€)</th>
|
<th>Montant (€)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range .Payments }}
|
{{ range .Payments }}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ .Date }}</td>
|
<td>{{ .Date }}</td>
|
||||||
<td>{{ .Method }}</td>
|
<td>{{ .Method }}</td>
|
||||||
<td>{{ .Amount }}</td>
|
<td>{{ .Amount }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="payment-summary space-between">
|
<div class="payment-summary space-between">
|
||||||
<table class="summary-table">
|
<table class="summary-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Montant Total :</strong></td>
|
<td><strong>Montant Total :</strong></td>
|
||||||
<td>{{ .Total }}</td>
|
<td>{{ .Total }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Montant Payé :</strong></td>
|
<td><strong>Montant Payé :</strong></td>
|
||||||
<td>{{ .AmountPaid }}</td>
|
<td>{{ .AmountPaid }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Solde Restant :</strong></td>
|
<td><strong>Solde Restant :</strong></td>
|
||||||
<td>{{ .BalanceDue }}</td>
|
<td>{{ .BalanceDue }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
<hr/>
|
|
||||||
<div class="order-summary space-between">
|
|
||||||
<div class="card">
|
|
||||||
<b>Notes</b> <br/>
|
|
||||||
TVA non applicable, art. 293 B du CGI <br/>
|
|
||||||
Dispensé d’immatriculation au registre du commerce et des sociétés (RCS)
|
|
||||||
et au répertoire des métiers. <br/>
|
|
||||||
Conditions de paiement : paiement à réception de facture. Aucun escompte
|
|
||||||
consenti pour règlement anticipé ou désistement. Tout incident de
|
|
||||||
paiement est passible d'intérêts de retard. Le montant des pénalités
|
|
||||||
résulte de l'application aux sommes restant dues d'un taux d'intérêt
|
|
||||||
légal en vigueur au moment de l'incident. <br/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<hr />
|
||||||
<div class="order-total space-between">
|
<div class="order-summary space-between">
|
||||||
<div></div>
|
<div class="card">
|
||||||
<div class="total">
|
<b>Notes</b> <br />
|
||||||
<div class="amount-due">Total</div>
|
TVA non applicable, art. 293 B du CGI <br />
|
||||||
<div class="amount-due-total">{{ .Total }}</div>
|
Dispensé d’immatriculation au registre du commerce et des sociétés (RCS)
|
||||||
|
et au répertoire des métiers. <br />
|
||||||
|
Conditions de paiement : paiement à réception de facture. Aucun escompte
|
||||||
|
consenti pour règlement anticipé ou désistement. Tout incident de
|
||||||
|
paiement est passible d'intérêts de retard. Le montant des pénalités
|
||||||
|
résulte de l'application aux sommes restant dues d'un taux d'intérêt
|
||||||
|
légal en vigueur au moment de l'incident. <br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="order-total space-between">
|
||||||
|
<div></div>
|
||||||
|
<div class="total">
|
||||||
|
<div class="amount-due">Total</div>
|
||||||
|
<div class="amount-due-total">{{ .Total }} €</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
</html>
|
||||||
|
|
@ -5,12 +5,13 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rjNemo/rentease/internal/config"
|
||||||
|
"github.com/rjNemo/rentease/internal/service/booking"
|
||||||
)
|
)
|
||||||
|
|
||||||
const invoiceTemplate = "assets/html/invoice.html"
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Define the invoice data structure
|
|
||||||
type Invoice struct {
|
type Invoice struct {
|
||||||
Host struct {
|
Host struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
@ -42,79 +43,37 @@ func main() {
|
||||||
Amount string
|
Amount string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Assume these values come from your application's context.
|
||||||
|
host := config.NewHost()
|
||||||
|
|
||||||
// Read the template file
|
booking := booking.Booking{
|
||||||
tmpl, err := template.ParseFiles(invoiceTemplate)
|
Name: "Michel Le Corre",
|
||||||
|
PhoneNumber: "+590 690 44 15 30",
|
||||||
|
CustomerNumber: 2,
|
||||||
|
Platform: "Privée",
|
||||||
|
From: time.Date(2025, time.January, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
To: time.Date(2025, time.March, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
Items: []booking.Item{
|
||||||
|
{Item: "T2", Quantity: 59, Price: 35.00},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get dynamic invoice data from the booking via Serialize.
|
||||||
|
invoiceData := booking.Serialize(host)
|
||||||
|
|
||||||
|
// Parse the HTML template.
|
||||||
|
tmpl, err := template.ParseFiles("assets/html/invoice.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error parsing template: %v", err)
|
log.Fatalf("Error parsing template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create sample invoice data
|
// Create a buffer to hold the rendered HTML.
|
||||||
invoice := Invoice{
|
|
||||||
Host: struct {
|
|
||||||
Name string
|
|
||||||
Address string
|
|
||||||
ZipCode string
|
|
||||||
City string
|
|
||||||
Phone string
|
|
||||||
Email string
|
|
||||||
}{
|
|
||||||
Name: "VillaFleurie",
|
|
||||||
Address: "4 rue Gerty Archimede",
|
|
||||||
ZipCode: "97190",
|
|
||||||
City: "Le Gosier",
|
|
||||||
Phone: "+590 690 44 15 30",
|
|
||||||
Email: "location.villafleurie@gmail.com",
|
|
||||||
},
|
|
||||||
Name: "Michel Le Corre",
|
|
||||||
//PhoneNumber: "+590 690 44 15 30",
|
|
||||||
CustomersNumber: 2,
|
|
||||||
Platform: "Privée",
|
|
||||||
ID: "VFNI0332",
|
|
||||||
From: "15 Janvier 2025",
|
|
||||||
To: "15 Mars 2025",
|
|
||||||
Total: "2065.00 €",
|
|
||||||
AmountPaid: "1565.00 €",
|
|
||||||
BalanceDue: "500.00 €",
|
|
||||||
Lines: []struct {
|
|
||||||
Name string
|
|
||||||
Quantity int
|
|
||||||
Price string
|
|
||||||
Total string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Name: "T2",
|
|
||||||
Quantity: 59,
|
|
||||||
Price: "35.00",
|
|
||||||
Total: "2065.00",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Payments: []struct {
|
|
||||||
Date string
|
|
||||||
Method string
|
|
||||||
Amount string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"–",
|
|
||||||
"Espèces",
|
|
||||||
"500.00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"–",
|
|
||||||
"Chèque",
|
|
||||||
"1065.00",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a buffer to store the rendered HTML
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
// Execute the template with the invoice data
|
if err := tmpl.Execute(&buf, invoiceData); err != nil {
|
||||||
if err := tmpl.Execute(&buf, invoice); err != nil {
|
|
||||||
log.Fatalf("Error executing template: %v", err)
|
log.Fatalf("Error executing template: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the rendered HTML to a file
|
// Write the rendered HTML to an output file.
|
||||||
outputPath := "output.html"
|
outputPath := "output.html"
|
||||||
if err := os.WriteFile(outputPath, buf.Bytes(), 0644); err != nil {
|
if err := os.WriteFile(outputPath, buf.Bytes(), 0644); err != nil {
|
||||||
log.Fatalf("Error writing HTML file: %v", err)
|
log.Fatalf("Error writing HTML file: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -40,3 +40,32 @@ func handleSync(bs *booking.Service) echo.HandlerFunc {
|
||||||
return c.JSON(http.StatusCreated, "👍")
|
return c.JSON(http.StatusCreated, "👍")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleCreateBooking(bs *booking.Service) echo.HandlerFunc {
|
||||||
|
type BookingInfo struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
log.Info("received booking sync request from booking")
|
||||||
|
x := c.Request().Body
|
||||||
|
body, err := io.ReadAll(x)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
log.Info(string(body))
|
||||||
|
|
||||||
|
bookingInfo := new(BookingInfo)
|
||||||
|
err = json.Unmarshal(body, bookingInfo)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, fmt.Sprintf("error unmarshalling JSON: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := bs.ParseFromApi(bookingInfo.Content)
|
||||||
|
if err != nil {
|
||||||
|
return c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
log.Infof("created booking %q from %q", b.Name, b.Platform)
|
||||||
|
|
||||||
|
return c.JSON(http.StatusCreated, "👍")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ func handlePdfCreateInvoice(bs *booking.Service, hc *config.Host) echo.HandlerFu
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.Attachment("tmp.pdf", fmt.Sprintf("VFNI-%s.pdf", b.InvoiceNumber(hc)))
|
return c.File("output.html")
|
||||||
|
// return c.File("output.html", fmt.Sprintf("VFNI-%s.pdf", b.InvoiceNumber(hc)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ func (s Server) MountHandlers() {
|
||||||
}))
|
}))
|
||||||
api.POST("/sync", handleSync(s.bs))
|
api.POST("/sync", handleSync(s.bs))
|
||||||
api.GET("/bookings", handleBookingList(s.bs))
|
api.GET("/bookings", handleBookingList(s.bs))
|
||||||
|
api.POST("/bookings", handleCreateBooking(s.bs))
|
||||||
|
|
||||||
private := s.Router.Group("")
|
private := s.Router.Group("")
|
||||||
private.Use(MakeAuthMiddleware(s.as))
|
private.Use(MakeAuthMiddleware(s.as))
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ func SentryTracingMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
sentry.WithOpName("http.server"),
|
sentry.WithOpName("http.server"),
|
||||||
sentry.ContinueFromRequest(c.Request()),
|
sentry.ContinueFromRequest(c.Request()),
|
||||||
sentry.WithTransactionSource(sentry.SourceURL),
|
sentry.WithTransactionSource(sentry.SourceURL),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction := sentry.StartTransaction(ctx,
|
transaction := sentry.StartTransaction(ctx,
|
||||||
|
|
|
||||||
37
internal/service/booking/invoice.go
Normal file
37
internal/service/booking/invoice.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package booking
|
||||||
|
|
||||||
|
type InvoiceLine struct {
|
||||||
|
Name string
|
||||||
|
Quantity int
|
||||||
|
Price string
|
||||||
|
Total string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaymentLine struct {
|
||||||
|
Date string
|
||||||
|
Method string
|
||||||
|
Amount string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Invoice struct {
|
||||||
|
Host struct {
|
||||||
|
Name string
|
||||||
|
Address string
|
||||||
|
ZipCode string
|
||||||
|
City string
|
||||||
|
Phone string
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
Name string
|
||||||
|
PhoneNumber string
|
||||||
|
CustomersNumber int
|
||||||
|
Platform string
|
||||||
|
ID string
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
Total string
|
||||||
|
AmountPaid string
|
||||||
|
BalanceDue string
|
||||||
|
Lines []InvoiceLine
|
||||||
|
Payments []PaymentLine
|
||||||
|
}
|
||||||
|
|
@ -49,34 +49,55 @@ func (b Booking) InvoiceNumber(hc *config.Host) string {
|
||||||
return fmt.Sprintf("%s%04s", hc.InvoicePrefix, strconv.Itoa(b.Id+hc.CustomerSeed))
|
return fmt.Sprintf("%s%04s", hc.InvoicePrefix, strconv.Itoa(b.Id+hc.CustomerSeed))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Booking) Serialize(hc *config.Host) map[string]any {
|
func (b Booking) ToInvoice(hc *config.Host) Invoice {
|
||||||
return map[string]any{
|
total := u.Reduce(b.Items, func(i Item, sum float64) float64 {
|
||||||
"host": map[string]any{
|
return sum + i.Price*float64(i.Quantity)
|
||||||
"name": hc.Name,
|
}, 0.0)
|
||||||
"address": hc.Address,
|
amountPaid := u.Reduce(b.Payments, func(i Payment, sum float64) float64 {
|
||||||
"zip": hc.ZipCode,
|
return sum + i.Amount
|
||||||
"city": hc.City,
|
}, 0.0)
|
||||||
"phone": hc.PhoneNumber,
|
|
||||||
"email": hc.Email,
|
return Invoice{
|
||||||
|
Host: struct {
|
||||||
|
Name string
|
||||||
|
Address string
|
||||||
|
ZipCode string
|
||||||
|
City string
|
||||||
|
Phone string
|
||||||
|
Email string
|
||||||
|
}{
|
||||||
|
Name: hc.Name,
|
||||||
|
Address: hc.Address,
|
||||||
|
ZipCode: hc.ZipCode,
|
||||||
|
City: hc.City,
|
||||||
|
Phone: hc.PhoneNumber,
|
||||||
|
Email: hc.Email,
|
||||||
},
|
},
|
||||||
"id": b.InvoiceNumber(hc),
|
Name: b.Name,
|
||||||
"name": b.Name,
|
PhoneNumber: b.PhoneNumber,
|
||||||
"phone_number": b.PhoneNumber,
|
CustomersNumber: b.CustomerNumber,
|
||||||
"customers_number": b.CustomerNumber,
|
Platform: b.Platform,
|
||||||
"platform": b.Platform,
|
ID: b.InvoiceNumber(hc),
|
||||||
"from": b.From.Format("02/01/2006"),
|
From: b.From.Format("02/01/2006"),
|
||||||
"to": b.To.Format("02/01/2006"),
|
To: b.To.Format("02/01/2006"),
|
||||||
"lines": u.Map(b.Items, func(i Item) map[string]any {
|
Total: strconv.FormatFloat(total, 'f', 2, 64),
|
||||||
return map[string]any{
|
AmountPaid: strconv.FormatFloat(amountPaid, 'f', 2, 64),
|
||||||
"name": i.ToFrench(),
|
BalanceDue: strconv.FormatFloat(total-amountPaid, 'f', 2, 64),
|
||||||
"quantity": i.Quantity,
|
Lines: u.Map(b.Items, func(i Item) InvoiceLine {
|
||||||
"price": i.Price,
|
return InvoiceLine{
|
||||||
"total": i.Price * float64(i.Quantity),
|
Name: i.ToFrench(),
|
||||||
|
Quantity: i.Quantity,
|
||||||
|
Price: strconv.FormatFloat(i.Price, 'f', 2, 64),
|
||||||
|
Total: strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Payments: u.Map(b.Payments, func(p Payment) PaymentLine {
|
||||||
|
return PaymentLine{
|
||||||
|
Date: p.CreatedAt.Format("02/01/2006"),
|
||||||
|
Method: p.PaymentMethod,
|
||||||
|
Amount: strconv.FormatFloat(p.Amount, 'f', 2, 64),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
"total": strconv.FormatFloat(u.Reduce(b.Items, func(i Item, sum float64) float64 {
|
|
||||||
return sum + i.Price*float64(i.Quantity)
|
|
||||||
}, 0.0), 'f', 2, 64),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
package booking
|
package booking
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rjNemo/rentease/internal/config"
|
"github.com/rjNemo/rentease/internal/config"
|
||||||
|
|
@ -101,5 +104,26 @@ func (bs Service) Cancel(id int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs Service) BuildInvoice(b *Booking, hc *config.Host) error {
|
func (bs Service) BuildInvoice(b *Booking, hc *config.Host) error {
|
||||||
return bs.pdf.BuildInvoice(b.Serialize(hc))
|
invoiceData := b.ToInvoice(hc)
|
||||||
|
log.Printf("%+v", invoiceData)
|
||||||
|
|
||||||
|
tmpl, err := template.ParseFiles("assets/html/invoice.html")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error parsing template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a buffer to hold the rendered HTML.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&buf, invoiceData); err != nil {
|
||||||
|
log.Fatalf("Error executing template: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the rendered HTML to an output file.
|
||||||
|
outputPath := "output.html"
|
||||||
|
if err := os.WriteFile(outputPath, buf.Bytes(), 0644); err != nil {
|
||||||
|
log.Fatalf("Error writing HTML file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("HTML file created successfully: %s", outputPath)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue