create invoice

This commit is contained in:
Ruidy 2025-02-04 08:06:15 +01:00
parent bfde4eb601
commit 7796f71590
No known key found for this signature in database
GPG key ID: E00F51288CB857CC
9 changed files with 302 additions and 227 deletions

View file

@ -1,5 +1,6 @@
<!doctype html> <!doctype html>
<html lang="fr"> <html lang="fr">
<head> <head>
<style> <style>
body { body {
@ -194,7 +195,7 @@
</tr> </tr>
<tr> <tr>
<td><strong>Montant Total :</strong></td> <td><strong>Montant Total :</strong></td>
<td>{{ .Total }}</td> <td>{{ .Total }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -281,8 +282,9 @@
<div></div> <div></div>
<div class="total"> <div class="total">
<div class="amount-due">Total</div> <div class="amount-due">Total</div>
<div class="amount-due-total">{{ .Total }}</div> <div class="amount-due-total">{{ .Total }}</div>
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -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)

View file

@ -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, "👍")
}
}

View file

@ -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)))
} }
} }

View file

@ -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))

View file

@ -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,

View 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
}

View file

@ -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),
} }
} }

View file

@ -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
} }