feat: manually create invoice

This commit is contained in:
Ruidy 2025-01-19 22:09:59 +01:00
parent 3568bc3e09
commit 7c3208c3b5
No known key found for this signature in database
GPG key ID: E00F51288CB857CC
6 changed files with 657 additions and 114 deletions

View file

@ -1,114 +1,287 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="main.css">
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f9f9f9;
color: #333;
}
.header {
background-color: #007b8f;
color: #fff;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
height: 60px;
}
.payee {
text-align: right;
}
hr {
border: none;
border-top: 2px solid #ddd;
margin: 20px 0;
}
.info-table {
width: 48%;
margin-bottom: 20px;
}
.info-table td {
padding: 5px 10px;
}
.billing-details {
display: flex;
justify-content: space-between;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.items-table th,
.items-table td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
.items-table th {
background-color: #007b8f;
color: #fff;
}
.items-table .align-right {
text-align: right;
}
.bg-gray {
background-color: #f4f4f4;
}
.payment-summary {
margin-top: 20px;
}
.summary-table {
width: 100%;
}
.summary-table td {
padding: 10px;
}
.payment-history h3 {
color: #007b8f;
}
.history-table {
width: 100%;
border-collapse: collapse;
}
.history-table th,
.history-table td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
.history-table th {
background-color: #007b8f;
color: #fff;
}
.card {
background-color: #eef7f9;
padding: 15px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.order-total {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 20px;
}
.total {
text-align: right;
}
.amount-due {
font-size: 18px;
font-weight: bold;
}
.amount-due-total {
font-size: 24px;
font-weight: bold;
color: #007b8f;
}
.space-between {
display: flex;
justify-content: space-between;
}
.text-break {
word-wrap: break-word;
}
</style>
</head>
<body>
<div class="header space-between">
<img class="logo" src="logo.png">
<img class="logo" src="assets/img/logo.png" />
<div class="payee">
<b>{{ host.name }}</b><br/>
{{ host.address }}<br/>
{{ host.zip_code}} {{ host.city }}<br/>
<b>Tel : </b>{{ host.phone }}<br/>
<b>Mail : </b>{{host.email }}<br/>
<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/>
<hr />
<div class="billing-details space-between">
<table class="info-table">
<tbody>
<tr>
<td>
<strong>{{name}}</strong></td>
<td><strong>{{ .Name }}</strong></td>
</tr>
<tr>
<td>
<strong>Tel : </strong></td>
<td>{{phone_number}}</td>
<td><strong>Tel:</strong></td>
<td>{{ .PhoneNumber }}</td>
</tr>
<tr>
<td>
<strong>Voyageurs : </strong></td>
<td>{{ customers_number }}</td>
<td><strong>Voyageurs:</strong></td>
<td>{{ .CustomersNumber }}</td>
</tr>
<tr>
<td>
<strong>Plateforme : </strong></td>
<td>{{platform}}</td>
<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>
<td><strong>Nº de facture:</strong></td>
<td>{{ .ID }}</td>
</tr>
<tr>
<td>
<strong>Du : </strong></td>
<td>{{ from }}</td>
<td><strong>Du:</strong></td>
<td>{{ .From }}</td>
</tr>
<tr>
<td>
<strong>Au : </strong></td>
<td>{{ to }}</td>
<td><strong>Au:</strong></td>
<td>{{ .To }}</td>
</tr>
<tr>
<td>
<strong>Montant : </strong></td>
<td>{{ total }}</td>
<td><strong>Montant Total:</strong></td>
<td>{{ .Total }}</td>
</tr>
</tbody>
</table>
</div>
<hr/>
<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>
<th>Prix (€)</th>
<th class="align-right">Total (€)</th>
</tr>
</thead>
<tbody class="bg-gray rounded">
{% for row in lines %}
{{ range .Lines }}
<tr class="item-row">
<td class="text-break product_name">
{{ row.name }}
</td>
<td>{{ row.quantity }}</td>
<td>{{row.price }}</td>
<td class="align-right">{{ row.total }}</td>
<td class="text-break product_name">{{ .Name }}</td>
<td>{{ .Quantity }}</td>
<td>{{ .Price }}</td>
<td class="align-right">{{ .Total }}</td>
</tr>
{% endfor %}
{{ 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é dimmatriculation 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/>
<b>Notes</b> <br />
TVA non applicable, art. 293 B du CGI <br />
Dispensé dimmatriculation 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 class="amount-due">Total</div>
<div class="amount-due-total">{{ .Total }}</div>
</div>
</div>
</body>

123
cmd/pdf/main.go Normal file
View file

@ -0,0 +1,123 @@
package main
import (
"bytes"
"html/template"
"log"
"os"
)
func main() {
// Define the invoice data structure
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 []struct {
Name string
Quantity int
Price string
Total string
}
Payments []struct {
Date string
Method string
Amount string
}
}
// Read the template file
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: "VillaFleurie",
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
// Execute the template with the invoice data
if err := tmpl.Execute(&buf, invoice); err != nil {
log.Fatalf("Error executing template: %v", err)
}
// Write the rendered HTML to a 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)
}

View file

@ -71,6 +71,16 @@ func (m *MockStore) UpdateItem(id int, item string, paymentMethod string, paymen
return args.Get(0).(*Item), args.Error(1)
}
func (m *MockStore) CreatePayment(p *Payment) (*Payment, error) {
args := m.Called(p)
return args.Get(0).(*Payment), args.Error(1)
}
// MockParserClient is a mock implementation of the parser.Client interface
type MockParserClient struct {
mock.Mock
}
// MockCalendarClient is a mock implementation of the calendar.Client interface
type MockCalendarClient struct {
mock.Mock

View file

@ -1,59 +0,0 @@
package booking_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/rjNemo/rentease/internal/service/booking"
)
const content = " Date d'arrivée jeu. 3 avr. 2025 Date de départ dim. 6 avr. 2025 Durée de séjour : 3 nuits Nombre de personnes : \n 2\n Nombre d'hébergements \n 1\n Montant total € 186 Nom du client : \n Olga Korovina\n \n ru\n \n okorov.905387@guest.booking.com\n Contactez vos clients ! Indiquez-leur l'heure à laquelle vous souhaitez les accueillir ou l'endroit où ils récupéreront leurs clés. Un simple appel suffit : Afficher le numéro de téléphone Vous pouvez également leur envoyer un e-mail ou un message. Langue préférée \n russe\n Canal : Booking.com Code IATA/TIDS : \n PC029090\n Numéro de réservation : \n 4453602306\n Montant soumis à commission : € 177 Reçu jeu. 2 janv. 2025 Commission : € 31,86 Bloc-notes (usage interne) Ajoutez une note ici \n\n Maison 1 Chambre T2 - VillaFleurie au bourg du Gosier)\n € 186 jeu. 3 avr. 2025 dim. 6 avr. 2025 Nom du client \n Olga Korovina\n Occupation maximum 2 adultes, 2 enfants (3 personnes max.) Photo de l'hébergement Date Tarif Tarif par nuit \n 03 - 04 avril\n \n Standard Rate\n € 59\n 04 - 05 avril\n \n Standard Rate\n € 59\n 05 - 06 avril\n \n Standard Rate\n € 59Sous-total € 177\n Taxe de séjour\n € 1.50 par personne et par nuit € 9Tarif total de l'hébergement € 186 Le tarif comprend 8.9 % de TVA Conversation avec le client \n Aucun message\n \n Les conversations avec vos clients apparaîtront ici.\n Booking.com reçoit tous les messages écrits ici et les traite selon sa Charte de confidentialité et informations sur les cookies Conditions "
func TestParseFromApi(t *testing.T) {
externalId := "4453602306"
tests := []struct {
name string
rawContent string
expected *booking.Booking
}{
{
name: "parse booking from raw content",
rawContent: content,
expected: &booking.Booking{
From: time.Date(2025, time.April, 3, 0, 0, 0, 0, time.UTC),
To: time.Date(2025, time.April, 6, 0, 0, 0, 0, time.UTC),
Name: "Olga Korovina",
Email: "okorov.905387@guest.booking.com",
Platform: "Booking",
CustomerNumber: 2,
PlatformFees: 31.86,
ExternalId: &externalId,
Canceled: false,
Items: []booking.Item{
{
BookingId: 0,
Item: "T2",
Quantity: 3,
Price: 59.0,
PaymentMethod: "Card",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
bs, _ := booking.NewService(&booking.MockStore{}, nil, nil, nil)
actual, err := bs.ParseFromApi(tt.rawContent)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
assert.Equal(t, tt.expected, actual, "expected %v, got %v", tt.expected, actual)
})
}
}

View file

@ -48,3 +48,7 @@ format:
# Lint the code
lint:
@golangci-lint run ./...
pdf:
@go run cmd/pdf/main.go
@open output.html

292
output.html Normal file
View file

@ -0,0 +1,292 @@
<!doctype html>
<html lang="en">
<head>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f9f9f9;
color: #333;
}
.header {
background-color: #007b8f;
color: #fff;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
height: 60px;
}
.payee {
text-align: right;
}
hr {
border: none;
border-top: 2px solid #ddd;
margin: 20px 0;
}
.info-table {
width: 48%;
margin-bottom: 20px;
}
.info-table td {
padding: 5px 10px;
}
.billing-details {
display: flex;
justify-content: space-between;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.items-table th,
.items-table td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
.items-table th {
background-color: #007b8f;
color: #fff;
}
.items-table .align-right {
text-align: right;
}
.bg-gray {
background-color: #f4f4f4;
}
.payment-summary {
margin-top: 20px;
}
.summary-table {
width: 100%;
}
.summary-table td {
padding: 10px;
}
.payment-history h3 {
color: #007b8f;
}
.history-table {
width: 100%;
border-collapse: collapse;
}
.history-table th,
.history-table td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
.history-table th {
background-color: #007b8f;
color: #fff;
}
.card {
background-color: #eef7f9;
padding: 15px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.order-total {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 20px;
}
.total {
text-align: right;
}
.amount-due {
font-size: 18px;
font-weight: bold;
}
.amount-due-total {
font-size: 24px;
font-weight: bold;
color: #007b8f;
}
.space-between {
display: flex;
justify-content: space-between;
}
.text-break {
word-wrap: break-word;
}
</style>
</head>
<body>
<div class="header space-between">
<img class="logo" src="assets/img/logo.png" />
<div class="payee">
<b>VillaFleurie</b><br />
4 rue Gerty Archimede<br />
97190 Le Gosier<br />
<b>Tel:</b> &#43;590 690 44 15 30<br />
<b>Mail:</b> location.villafleurie@gmail.com<br />
</div>
</div>
<hr />
<div class="billing-details space-between">
<table class="info-table">
<tbody>
<tr>
<td><strong>VillaFleurie</strong></td>
</tr>
<tr>
<td><strong>Tel:</strong></td>
<td>&#43;590 690 44 15 30</td>
</tr>
<tr>
<td><strong>Voyageurs:</strong></td>
<td>2</td>
</tr>
<tr>
<td><strong>Plateforme:</strong></td>
<td>Privée</td>
</tr>
</tbody>
</table>
<table class="info-table">
<tbody>
<tr>
<td><strong>Nº de facture:</strong></td>
<td>VFNI0332</td>
</tr>
<tr>
<td><strong>Du:</strong></td>
<td>15 Janvier 2025</td>
</tr>
<tr>
<td><strong>Au:</strong></td>
<td>15 Mars 2025</td>
</tr>
<tr>
<td><strong>Montant Total:</strong></td>
<td>2065.00</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">
<tr class="item-row">
<td class="text-break product_name">T2</td>
<td>59</td>
<td>35.00</td>
<td class="align-right">2065.00</td>
</tr>
</tbody>
</table>
</div>
<div class="payment-summary space-between">
<table class="summary-table">
<tbody>
<tr>
<td><strong>Montant Total:</strong></td>
<td>2065.00</td>
</tr>
<tr>
<td><strong>Montant Payé:</strong></td>
<td>1565.00</td>
</tr>
<tr>
<td><strong>Solde Restant:</strong></td>
<td>500.00</td>
</tr>
</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>
<tr>
<td></td>
<td>Espèces</td>
<td>500.00</td>
</tr>
<tr>
<td></td>
<td>Cheque</td>
<td>1565.00</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é dimmatriculation 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">2065.00</div>
</div>
</div>
</body>
</html>