mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +00:00
Create taxes for taxable items automatically (#16)
* refactor return error when building booking service * fix the description * set taxable item by amount * auto create tax items if the item is taxable * fix linter * remove legacy tax entry * display multiple items * use the price from the form * improve item sorting * lintfix
This commit is contained in:
parent
04be887ad8
commit
d4e6b35a96
7 changed files with 65 additions and 47 deletions
|
|
@ -1,9 +1,9 @@
|
|||
FROM golang:1.23-alpine AS builder
|
||||
|
||||
RUN apk update && apk add --no-cache \
|
||||
build-base \
|
||||
ca-certificates \
|
||||
&& update-ca-certificates
|
||||
build-base \
|
||||
ca-certificates \
|
||||
&& update-ca-certificates
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ type HostItem struct {
|
|||
// If true, the item will be added to the calendar
|
||||
MustSyncCalendar bool
|
||||
HasEndDate bool
|
||||
// If true, a tax item will be added to the invoice
|
||||
Taxable bool // TODO: create taxes auto if taxable item
|
||||
// Amount of taxes in EUR. If not zero, a tax item will be added to the invoice
|
||||
Taxes float64
|
||||
}
|
||||
|
||||
func NewHost() *Host {
|
||||
|
|
@ -47,7 +47,7 @@ func NewHost() *Host {
|
|||
CalendarId: os.Getenv("CALENDAR_ID_T2"),
|
||||
MustSyncCalendar: true,
|
||||
HasEndDate: true,
|
||||
Taxable: true,
|
||||
Taxes: 1.5,
|
||||
},
|
||||
|
||||
"T3": {
|
||||
|
|
@ -56,7 +56,7 @@ func NewHost() *Host {
|
|||
CalendarId: os.Getenv("CALENDAR_ID_T3"),
|
||||
MustSyncCalendar: true,
|
||||
HasEndDate: true,
|
||||
Taxable: true,
|
||||
Taxes: 1.5,
|
||||
},
|
||||
"Airport": {
|
||||
Name: "Airport",
|
||||
|
|
@ -70,10 +70,6 @@ func NewHost() *Host {
|
|||
Name: "Transport",
|
||||
Price: 20.0,
|
||||
},
|
||||
"Taxes": { // TODO: remove after auto creation enabled
|
||||
Name: "Taxes",
|
||||
Price: 1.5,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ type Service struct {
|
|||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewService(db *gorm.DB) *Service {
|
||||
return &Service{db: db}
|
||||
func NewService(db *gorm.DB) (*Service, error) {
|
||||
return &Service{db: db}, nil
|
||||
}
|
||||
|
||||
func (bs Service) All() []*Line {
|
||||
|
|
@ -97,16 +97,30 @@ func (bs Service) Update(id int, From time.Time, To time.Time, Name string, Phon
|
|||
return b
|
||||
}
|
||||
|
||||
func (bs Service) CreateItem(bid int, item string, qty int, price float64, method string) *Item {
|
||||
func (bs Service) CreateItem(bookingId int, item config.HostItem, quantity int, price float64, paymentMethod string, customerNumber int) (items []*Item) {
|
||||
i := &Item{
|
||||
BookingId: bid,
|
||||
Item: item,
|
||||
Quantity: qty,
|
||||
BookingId: bookingId,
|
||||
Item: item.Name,
|
||||
Quantity: quantity,
|
||||
Price: price,
|
||||
PaymentMethod: method,
|
||||
PaymentMethod: paymentMethod,
|
||||
}
|
||||
_ = bs.db.Create(i)
|
||||
return i
|
||||
items = append(items, i)
|
||||
|
||||
if item.Taxes != 0.0 {
|
||||
ti := &Item{
|
||||
BookingId: bookingId,
|
||||
Item: "Taxes",
|
||||
Quantity: quantity * customerNumber,
|
||||
Price: item.Taxes,
|
||||
PaymentMethod: "Cash",
|
||||
}
|
||||
_ = bs.db.Create(ti)
|
||||
items = append(items, ti)
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func (bs Service) PayItem(id int) *Item {
|
||||
|
|
@ -257,20 +271,19 @@ func (bs Service) ParseFromApi(rawContent string) (*Booking, error) {
|
|||
arrivalDate := extractDate(`Date d'arrivée `, content)
|
||||
departureDate := extractDate(`Date de départ `, content)
|
||||
stayLength := extractInt(`Durée de séjour : (\d+) nuits`, content)
|
||||
totalAmount := extractFloat(`Montant total € (\d+)`, content)
|
||||
customerName := extractString(`Nom du client : \n\s+([\w\s]+)`, content)
|
||||
customerName = strings.SplitN(customerName, "\n", 2)[0]
|
||||
customerEmail := extractString(`[\w\.\-]+@[\w\.\-]+\.\w+`, content)
|
||||
customerNumber := extractInt(`Nombre de personnes : \s*\n\s*(\d+)`, content)
|
||||
commissionAmount := extractFloat(`Commission : € (\d+,\d+)`, content)
|
||||
item := extractString(`Maison 1 Chambre \((T2|T3) -`, content)
|
||||
itemName := extractString(`Maison 1 Chambre \((T2|T3) -`, content)
|
||||
externalId := extractString(`Numéro de réservation : \n\s+(\d+)`, content)
|
||||
standardRate := extractFloat(`Standard Rate\n\s+€ (\d+)`, content)
|
||||
taxQty := (totalAmount - standardRate*float64(stayLength)) / 1.5
|
||||
|
||||
b := bs.Create(*formatDate(arrivalDate), *formatDate(departureDate), customerName, "", customerEmail, "Booking", customerNumber, commissionAmount, &externalId)
|
||||
bs.CreateItem(b.Id, item, stayLength, standardRate, "Card")
|
||||
bs.CreateItem(b.Id, "Taxes", int(taxQty), 1.5, "Cash")
|
||||
if item, ok := config.NewHost().Items[itemName]; ok {
|
||||
bs.CreateItem(b.Id, item, stayLength, standardRate, "Card", customerNumber)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ func JobMonthlyBookingReport() error {
|
|||
|
||||
now := time.Now()
|
||||
log.Println("Start Monthly Booking Report job at:", now)
|
||||
report := booking.NewService(db).BuildReport("monthly", int(now.Month()), now.Year())
|
||||
service, _ := booking.NewService(db)
|
||||
report := service.BuildReport("monthly", int(now.Month()), now.Year())
|
||||
|
||||
ps, err := pdf.NewPdfService(
|
||||
os.Getenv("HTMLDOCS_PROJECT_ID"),
|
||||
|
|
|
|||
|
|
@ -139,13 +139,13 @@ func handleBookingPage(bs *booking.Service, hc *config.Host) echo.HandlerFunc {
|
|||
return sum + i.Price*float64(i.Quantity)
|
||||
}, 0.0), 'f', 2, 64),
|
||||
Platforms: hc.Platforms,
|
||||
ItemList: u.OrderBy(func(items map[string]config.HostItem) (out []string) {
|
||||
ItemList: u.OrderBy(func(items map[string]config.HostItem) (out []string) { // TODO: return the full item to prefill the form
|
||||
for _, item := range items {
|
||||
out = append(out, item.Name)
|
||||
}
|
||||
return out
|
||||
}(hc.Items),
|
||||
func(l, r string) bool { return l < r },
|
||||
func(l, r string) bool { return l > r },
|
||||
),
|
||||
PaymentMethods: hc.PaymentMethods,
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ func handleCreateItem(bs *booking.Service, cs *calendar.Service, hc *config.Host
|
|||
return fmt.Errorf("invalid item name %q", ni.Item)
|
||||
}
|
||||
|
||||
i := bs.CreateItem(b.Id, ni.Item, ni.Quantity, ni.Price, ni.PaymentMethod)
|
||||
newItems := bs.CreateItem(b.Id, itm, ni.Quantity, ni.Price, ni.PaymentMethod, b.CustomerNumber)
|
||||
|
||||
if err = cs.Create(
|
||||
itm.CalendarId,
|
||||
|
|
@ -269,16 +269,20 @@ func handleCreateItem(bs *booking.Service, cs *calendar.Service, hc *config.Host
|
|||
captureError(c, err)
|
||||
}
|
||||
|
||||
return renderTempl(c, http.StatusCreated, view.LineItem(&view.ItemViewModel{
|
||||
Id: strconv.Itoa(i.Id),
|
||||
Item: i.Item,
|
||||
Quantity: strconv.Itoa(i.Quantity),
|
||||
Price: strconv.FormatFloat(i.Price, 'f', 2, 64),
|
||||
PaymentMethod: i.PaymentMethod,
|
||||
PaymentStatus: i.PaymentStatus,
|
||||
SubTotal: strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
|
||||
ItemUrl: fmt.Sprintf("%s/%d", constant.RouteItem, i.Id),
|
||||
}))
|
||||
for _, i := range newItems {
|
||||
_ = renderTempl(c, http.StatusCreated, view.LineItem(&view.ItemViewModel{
|
||||
Id: strconv.Itoa(i.Id),
|
||||
Item: i.Item,
|
||||
Quantity: strconv.Itoa(i.Quantity),
|
||||
Price: strconv.FormatFloat(i.Price, 'f', 2, 64),
|
||||
PaymentMethod: i.PaymentMethod,
|
||||
PaymentStatus: i.PaymentStatus,
|
||||
SubTotal: strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
|
||||
ItemUrl: fmt.Sprintf("%s/%d", constant.RouteItem, i.Id),
|
||||
}))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ func PublicLayout() templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" | Locations de vacances au Gosier en Guadeloupe</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"Locations de vacances au Gosier en Guadeloupe\"><link rel=\"icon\" href=\"/static/icons/favicon.png\"><link rel=\"stylesheet\" href=\"/static/css/pico.min.css\"><link rel=\"stylesheet\" href=\"/static/css/auth.css\"><script src=\"/static/js/htmx.js\" defer></script></head><body hx-boost=\"false\"><nav class=\"container-fluid\"><ul><li><a href=\"/\"><img src=\"/static/img/logo.png\" alt=\"logo de villafleurie\" width=\"50px\"></a></li></ul><ul><li><details class=\"dropdown\"><summary>Logements</summary><ul dir=\"rtl\">")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" | Locations de vacances au Gosier en Guadeloupe</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"Locations de vacances au Gosier en Guadeloupe\"><link rel=\"icon\" href=\"/static/icons/favicon.png\"><link rel=\"stylesheet\" href=\"/static/css/pico.min.css\"><script src=\"/static/js/htmx.js\" defer></script></head><body hx-boost=\"false\"><nav class=\"container-fluid\"><ul><li><a href=\"/\"><img src=\"/static/img/logo.png\" alt=\"logo de villafleurie\" width=\"50px\"></a></li></ul><ul><li><details class=\"dropdown\"><summary>Logements</summary><ul dir=\"rtl\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ func PublicLayout() templ.Component {
|
|||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(l.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/layout/public.templ`, Line: 64, Col: 39}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/layout/public.templ`, Line: 63, Col: 39}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -121,7 +121,7 @@ func PublicLayout() templ.Component {
|
|||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(hvm.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/layout/public.templ`, Line: 76, Col: 21}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/layout/public.templ`, Line: 75, Col: 21}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -143,7 +143,7 @@ func PublicLayout() templ.Component {
|
|||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(hvm.PhoneNumber)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/layout/public.templ`, Line: 76, Col: 93}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/layout/public.templ`, Line: 75, Col: 93}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
@ -165,7 +165,7 @@ func PublicLayout() templ.Component {
|
|||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(hvm.Email)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/layout/public.templ`, Line: 76, Col: 148}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/layout/public.templ`, Line: 75, Col: 148}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
|
|
|||
12
main.go
12
main.go
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
|
|
@ -54,12 +53,17 @@ func run(c context.Context, getEnv func(string) string) error {
|
|||
return fmt.Errorf("error connecting to the database %s", err)
|
||||
}
|
||||
|
||||
// build booking service
|
||||
err = db.AutoMigrate(&booking.Booking{}, &booking.BookingRequest{}, &booking.Item{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error migrating the database %s", err)
|
||||
}
|
||||
|
||||
// build booking service
|
||||
bs, err := booking.NewService(db)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error starting booking service %s", err)
|
||||
}
|
||||
|
||||
// build pdf service
|
||||
ps, err := pdf.NewPdfService(
|
||||
getEnv("HTMLDOCS_PROJECT_ID"),
|
||||
|
|
@ -85,7 +89,7 @@ func run(c context.Context, getEnv func(string) string) error {
|
|||
// build calendar service
|
||||
cs, err := calendar.NewService(ctx, getEnv("CALENDAR_CREDENTIALS"))
|
||||
if err != nil {
|
||||
log.Fatalf("error starting calendar service %s", err)
|
||||
return fmt.Errorf("error starting calendar service %s", err)
|
||||
}
|
||||
|
||||
// starting server
|
||||
|
|
@ -99,7 +103,7 @@ func run(c context.Context, getEnv func(string) string) error {
|
|||
origins := strings.Split(ogs, ",")
|
||||
|
||||
srv, err := server.New(
|
||||
booking.NewService(db), // TODO: should validate the booking service building
|
||||
bs,
|
||||
as,
|
||||
ps,
|
||||
cs,
|
||||
|
|
|
|||
Loading…
Reference in a new issue