mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +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>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
|
|
@ -147,142 +148,143 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<div class="header space-between">
|
||||
<img class="logo" src="assets/img/logo.png"/>
|
||||
<div class="payee">
|
||||
<b>{{ .Host.Name }}</b><br/>
|
||||
{{ .Host.Address }}<br/>
|
||||
{{ .Host.ZipCode }} {{ .Host.City }}<br/>
|
||||
<b>Tel :</b> {{ .Host.Phone }}<br/>
|
||||
<b>Mail :</b> {{ .Host.Email }}<br/>
|
||||
<div class="header space-between">
|
||||
<img class="logo" src="assets/img/logo.png" />
|
||||
<div class="payee">
|
||||
<b>{{ .Host.Name }}</b><br />
|
||||
{{ .Host.Address }}<br />
|
||||
{{ .Host.ZipCode }} {{ .Host.City }}<br />
|
||||
<b>Tel :</b> {{ .Host.Phone }}<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>
|
||||
<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">
|
||||
<h3>Historique des Paiements</h3>
|
||||
<table class="history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Mode de Paiement</th>
|
||||
<th>Montant (€)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Payments }}
|
||||
<tr>
|
||||
<td>{{ .Date }}</td>
|
||||
<td>{{ .Method }}</td>
|
||||
<td>{{ .Amount }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="payment-history">
|
||||
<h3>Historique des Paiements</h3>
|
||||
<table class="history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Mode de Paiement</th>
|
||||
<th>Montant (€)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Payments }}
|
||||
<tr>
|
||||
<td>{{ .Date }}</td>
|
||||
<td>{{ .Method }}</td>
|
||||
<td>{{ .Amount }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="payment-summary space-between">
|
||||
<table class="summary-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Montant Total :</strong></td>
|
||||
<td>{{ .Total }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Montant Payé :</strong></td>
|
||||
<td>{{ .AmountPaid }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Solde Restant :</strong></td>
|
||||
<td>{{ .BalanceDue }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</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 class="payment-summary space-between">
|
||||
<table class="summary-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Montant Total :</strong></td>
|
||||
<td>{{ .Total }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Montant Payé :</strong></td>
|
||||
<td>{{ .AmountPaid }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Solde Restant :</strong></td>
|
||||
<td>{{ .BalanceDue }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
<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 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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
|
|
@ -5,12 +5,13 @@ import (
|
|||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/rjNemo/rentease/internal/config"
|
||||
"github.com/rjNemo/rentease/internal/service/booking"
|
||||
)
|
||||
|
||||
const invoiceTemplate = "assets/html/invoice.html"
|
||||
|
||||
func main() {
|
||||
// Define the invoice data structure
|
||||
type Invoice struct {
|
||||
Host struct {
|
||||
Name string
|
||||
|
|
@ -42,79 +43,37 @@ func main() {
|
|||
Amount string
|
||||
}
|
||||
}
|
||||
// Assume these values come from your application's context.
|
||||
host := config.NewHost()
|
||||
|
||||
// Read the template file
|
||||
tmpl, err := template.ParseFiles(invoiceTemplate)
|
||||
booking := booking.Booking{
|
||||
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 {
|
||||
log.Fatalf("Error parsing template: %v", err)
|
||||
}
|
||||
|
||||
// Create sample invoice data
|
||||
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
|
||||
// Create a buffer to hold the rendered HTML.
|
||||
var buf bytes.Buffer
|
||||
// Execute the template with the invoice data
|
||||
if err := tmpl.Execute(&buf, invoice); err != nil {
|
||||
if err := tmpl.Execute(&buf, invoiceData); err != nil {
|
||||
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"
|
||||
if err := os.WriteFile(outputPath, buf.Bytes(), 0644); err != nil {
|
||||
log.Fatalf("Error writing HTML file: %v", err)
|
||||
|
|
|
|||
|
|
@ -40,3 +40,32 @@ func handleSync(bs *booking.Service) echo.HandlerFunc {
|
|||
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 {
|
||||
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.GET("/bookings", handleBookingList(s.bs))
|
||||
api.POST("/bookings", handleCreateBooking(s.bs))
|
||||
|
||||
private := s.Router.Group("")
|
||||
private.Use(MakeAuthMiddleware(s.as))
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ func SentryTracingMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
|||
sentry.WithOpName("http.server"),
|
||||
sentry.ContinueFromRequest(c.Request()),
|
||||
sentry.WithTransactionSource(sentry.SourceURL),
|
||||
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
func (b Booking) Serialize(hc *config.Host) map[string]any {
|
||||
return map[string]any{
|
||||
"host": map[string]any{
|
||||
"name": hc.Name,
|
||||
"address": hc.Address,
|
||||
"zip": hc.ZipCode,
|
||||
"city": hc.City,
|
||||
"phone": hc.PhoneNumber,
|
||||
"email": hc.Email,
|
||||
func (b Booking) ToInvoice(hc *config.Host) Invoice {
|
||||
total := u.Reduce(b.Items, func(i Item, sum float64) float64 {
|
||||
return sum + i.Price*float64(i.Quantity)
|
||||
}, 0.0)
|
||||
amountPaid := u.Reduce(b.Payments, func(i Payment, sum float64) float64 {
|
||||
return sum + i.Amount
|
||||
}, 0.0)
|
||||
|
||||
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,
|
||||
"phone_number": b.PhoneNumber,
|
||||
"customers_number": b.CustomerNumber,
|
||||
"platform": b.Platform,
|
||||
"from": b.From.Format("02/01/2006"),
|
||||
"to": b.To.Format("02/01/2006"),
|
||||
"lines": u.Map(b.Items, func(i Item) map[string]any {
|
||||
return map[string]any{
|
||||
"name": i.ToFrench(),
|
||||
"quantity": i.Quantity,
|
||||
"price": i.Price,
|
||||
"total": i.Price * float64(i.Quantity),
|
||||
Name: b.Name,
|
||||
PhoneNumber: b.PhoneNumber,
|
||||
CustomersNumber: b.CustomerNumber,
|
||||
Platform: b.Platform,
|
||||
ID: b.InvoiceNumber(hc),
|
||||
From: b.From.Format("02/01/2006"),
|
||||
To: b.To.Format("02/01/2006"),
|
||||
Total: strconv.FormatFloat(total, 'f', 2, 64),
|
||||
AmountPaid: strconv.FormatFloat(amountPaid, 'f', 2, 64),
|
||||
BalanceDue: strconv.FormatFloat(total-amountPaid, 'f', 2, 64),
|
||||
Lines: u.Map(b.Items, func(i Item) InvoiceLine {
|
||||
return InvoiceLine{
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
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