mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +00:00
355 lines
9.5 KiB
Go
355 lines
9.5 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/a-h/templ"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/labstack/gommon/log"
|
|
u "github.com/rjNemo/underscore"
|
|
|
|
"github.com/rjNemo/rentease/constants"
|
|
"github.com/rjNemo/rentease/internal/domains/booking"
|
|
"github.com/rjNemo/rentease/internal/views"
|
|
myTime "github.com/rjNemo/rentease/pkg/time"
|
|
)
|
|
|
|
func (s Server) handleHomePage() echo.HandlerFunc {
|
|
return func(ctx echo.Context) error {
|
|
component := views.Index()
|
|
return s.renderTempl(ctx, http.StatusOK, component)
|
|
}
|
|
}
|
|
|
|
func (s Server) handleListBookingPage() echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
bookings := make([]*booking.Booking, 0)
|
|
_ = s.db.Order("id desc").Find(&bookings)
|
|
|
|
bvm := u.Map(bookings, func(b *booking.Booking) *views.ListBookingsViewModel {
|
|
return &views.ListBookingsViewModel{
|
|
Id: strconv.Itoa(b.Id),
|
|
Url: templ.SafeURL(fmt.Sprintf("%s/%d", constants.RouteBooking, b.Id)),
|
|
From: b.From.Format("2006-01-02"),
|
|
To: b.To.Format("2006-01-02"),
|
|
Platform: b.Platform,
|
|
Name: b.Name,
|
|
}
|
|
})
|
|
|
|
component := views.ListBookings(bvm)
|
|
return s.renderTempl(c, http.StatusOK, component)
|
|
}
|
|
}
|
|
|
|
func (s Server) handleNewBookingPage() echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
component := views.NewBooking(constants.Platforms)
|
|
return s.renderTempl(c, http.StatusOK, component)
|
|
}
|
|
}
|
|
|
|
func (s Server) handleCreateBooking() echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
type NewBooking struct {
|
|
From time.Time `json:"from"`
|
|
To time.Time `from:"to"`
|
|
Name string `form:"name"`
|
|
PhoneNumber string `form:"phone_number"`
|
|
Email string `form:"email"`
|
|
Platform string `form:"platform"`
|
|
CustomerNumber int `form:"customer_number"`
|
|
PlatformFees float64 `form:"platform_fees"`
|
|
}
|
|
nb := new(NewBooking)
|
|
err := c.Bind(nb)
|
|
if err != nil {
|
|
log.Warn(err)
|
|
return err
|
|
}
|
|
|
|
ts, _ := myTime.ParseFromForm(c.FormValue("from"))
|
|
nb.From = ts
|
|
ts, _ = myTime.ParseFromForm(c.FormValue("to"))
|
|
nb.To = ts
|
|
|
|
b := &booking.Booking{
|
|
Name: nb.Name,
|
|
PhoneNumber: nb.PhoneNumber,
|
|
CustomerNumber: nb.CustomerNumber,
|
|
Email: nb.Email,
|
|
From: nb.From,
|
|
To: nb.To,
|
|
Platform: nb.Platform,
|
|
PlatformFees: nb.PlatformFees,
|
|
}
|
|
_ = s.db.Create(b)
|
|
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s/%d", constants.RouteBooking, b.Id))
|
|
}
|
|
}
|
|
|
|
func (s Server) handleBookingPage() echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b := &booking.Booking{Id: id}
|
|
s.db.Preload("Items").First(b)
|
|
|
|
bvm := &views.BookingViewModel{
|
|
Id: fmt.Sprintf("%04s", strconv.Itoa(b.Id)),
|
|
Name: b.Name,
|
|
PhoneNumber: b.PhoneNumber,
|
|
CustomerNumber: strconv.Itoa(b.CustomerNumber),
|
|
Email: b.Email,
|
|
From: b.From.Format("2006-01-02"),
|
|
To: b.To.Format("2006-01-02"),
|
|
Platform: b.Platform,
|
|
PlatformFees: strconv.FormatFloat(b.PlatformFees, 'f', 2, 64),
|
|
Url: templ.EscapeString(fmt.Sprintf("%s/%d/items", constants.RouteBooking, b.Id)),
|
|
Items: u.Map(b.Items, func(i booking.Item) views.ItemViewModel {
|
|
return views.ItemViewModel{
|
|
Item: i.Item,
|
|
Quantity: strconv.Itoa(i.Quantity),
|
|
Price: strconv.FormatFloat(i.Price, 'f', 2, 64),
|
|
SubTotal: strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
|
|
PaymentMethod: i.PaymentMethod,
|
|
PaymentStatus: i.PaymentStatus,
|
|
}
|
|
}),
|
|
Total: strconv.FormatFloat(u.Reduce(b.Items, func(i booking.Item, sum float64) float64 {
|
|
return sum + i.Price*float64(i.Quantity)
|
|
}, 0.0), 'f', 2, 64),
|
|
Platforms: constants.Platforms,
|
|
ItemList: constants.Items,
|
|
PaymentMethods: constants.PaymentMethods,
|
|
}
|
|
component := views.BookingById(bvm)
|
|
return s.renderTempl(c, http.StatusOK, component)
|
|
}
|
|
}
|
|
|
|
func (s Server) handleCreateItem() echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
bookingIdStr := c.Param("id")
|
|
bid, err := strconv.Atoi(bookingIdStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
type NewItem struct {
|
|
Item string `form:"item"`
|
|
Quantity int `form:"quantity"`
|
|
Price float64 `form:"price"`
|
|
PaymentMethod string `form:"method"`
|
|
}
|
|
ni := new(NewItem)
|
|
if err := c.Bind(ni); err != nil {
|
|
log.Warn(err)
|
|
return err
|
|
}
|
|
|
|
i := &booking.Item{
|
|
BookingId: bid,
|
|
Item: ni.Item,
|
|
Quantity: ni.Quantity,
|
|
Price: ni.Price,
|
|
PaymentMethod: ni.PaymentMethod,
|
|
}
|
|
_ = s.db.Create(i)
|
|
return s.renderTempl(c, http.StatusCreated, views.LineItem(i))
|
|
}
|
|
}
|
|
|
|
func (s Server) handleReportsPage() echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
period := c.QueryParam("period")
|
|
if !u.Contains([]string{"month", "year"}, period) {
|
|
period = "month"
|
|
}
|
|
|
|
monthStr := c.QueryParam("month")
|
|
month, err := strconv.Atoi(monthStr)
|
|
if err != nil || month < 1 || month > 12 {
|
|
month = int(time.Now().Month())
|
|
}
|
|
|
|
yearStr := c.QueryParam("year")
|
|
_, err = strconv.Atoi(yearStr)
|
|
if err != nil {
|
|
yearStr = time.Now().Format("2006")
|
|
}
|
|
return s.renderTempl(c, http.StatusOK, views.Reports(constants.Months, yearStr))
|
|
}
|
|
}
|
|
|
|
func (s Server) handleComputeReport() echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
period := c.FormValue("period")
|
|
if !u.Contains([]string{"month", "year"}, period) {
|
|
return &echo.HTTPError{
|
|
Code: http.StatusBadRequest,
|
|
Message: fmt.Sprintf("%q is not a valid period", period),
|
|
}
|
|
}
|
|
|
|
monthStr := c.FormValue("month")
|
|
month, err := strconv.Atoi(monthStr)
|
|
if err != nil || month < 1 || month > 12 {
|
|
return &echo.HTTPError{
|
|
Code: http.StatusBadRequest,
|
|
Message: fmt.Sprintf("%q is not a valid month", month),
|
|
}
|
|
}
|
|
|
|
yearStr := c.FormValue("year")
|
|
year, err := strconv.Atoi(yearStr)
|
|
if err != nil {
|
|
return &echo.HTTPError{
|
|
Code: http.StatusBadRequest,
|
|
Message: fmt.Sprintf("%q is not a valid year", year),
|
|
}
|
|
}
|
|
|
|
type Result struct {
|
|
From time.Time
|
|
To time.Time
|
|
Id string
|
|
CustomerName string
|
|
Platform string
|
|
Total float64
|
|
PlatformFees float64
|
|
}
|
|
res := make([]*Result, 0)
|
|
var startDate time.Time
|
|
var endDate time.Time
|
|
|
|
if period == "month" {
|
|
startDate = time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
|
|
endDate = time.Date(year, time.Month(month), 31, 0, 0, 0, 0, time.UTC)
|
|
} else {
|
|
startDate = time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
|
|
endDate = time.Date(year, time.December, 31, 0, 0, 0, 0, time.UTC)
|
|
}
|
|
|
|
s.db.
|
|
Raw(`
|
|
select id, customer_name, "from", "to", platform, total, platform_fees
|
|
from (select sum(price) as total, booking_id
|
|
from bookings
|
|
join items on bookings.id = items.booking_id
|
|
where "from" between ? and ?
|
|
group by booking_id) as sbi,
|
|
(select * from bookings) as b
|
|
where sbi.booking_id = b.id;
|
|
`,
|
|
startDate.Format("2006-01-02"), endDate.Format("2006-01-02")).
|
|
Scan(&res)
|
|
|
|
reportVm := u.Map(res, func(r *Result) *views.ReportViewModel {
|
|
return &views.ReportViewModel{
|
|
Id: r.Id,
|
|
Url: templ.SafeURL(fmt.Sprintf("%s/%s", constants.RouteBooking, r.Id)),
|
|
Total: strconv.FormatFloat(r.Total, 'f', 2, 64),
|
|
CustomerName: r.CustomerName,
|
|
From: r.From.Format("2006-01-02"),
|
|
To: r.To.Format("2006-01-02"),
|
|
Platform: r.Platform,
|
|
PlatformFees: strconv.FormatFloat(r.PlatformFees, 'f', 2, 64),
|
|
}
|
|
})
|
|
return s.renderTempl(c, http.StatusOK, views.ReportSection(reportVm))
|
|
}
|
|
}
|
|
|
|
func (s Server) handleCreateInvoicePdf() echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
data := struct {
|
|
Context map[string]any `json:"context"`
|
|
Path string `json:"path"`
|
|
ProjectId string `json:"projectId"`
|
|
}{
|
|
Context: map[string]any{},
|
|
Path: "index.html", // TODO: put in a variable
|
|
ProjectId: os.Getenv("HTMLDOCS_PROJECT_ID"),
|
|
}
|
|
|
|
payload, err := json.Marshal(data)
|
|
if err != nil {
|
|
log.Warnf("Error marshalling JSON:", err)
|
|
return err
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", os.Getenv("HTMLDOCS_URL"), bytes.NewBuffer(payload))
|
|
if err != nil {
|
|
log.Warnf("Error creating request:", err)
|
|
return err
|
|
}
|
|
|
|
req.Header.Set("Authorization", "Bearer "+os.Getenv("HTMLDOCS_KEY"))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
log.Warnf("Error sending request:", err)
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
log.Warnf("Response Status:", resp.Status)
|
|
res := new(struct {
|
|
Url string `json:"url"`
|
|
Error string `json:"error"`
|
|
})
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = json.Unmarshal(body, res)
|
|
if err != nil {
|
|
log.Warnf("error decoding response: %s", err)
|
|
return err
|
|
}
|
|
|
|
if res.Error != "" {
|
|
log.Warnf("error building pdf file %s", err)
|
|
return errors.New(res.Error)
|
|
}
|
|
|
|
resp, err = http.Get(res.Url)
|
|
if err != nil {
|
|
log.Warnf("Error retrieving file")
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
file, err := os.Create("tmp.pdf")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer file.Close()
|
|
|
|
body, err = io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = file.Write(body)
|
|
if err != nil {
|
|
log.Error("Error copying file content")
|
|
return err
|
|
}
|
|
return c.Attachment("tmp.pdf", "tmp.pdf")
|
|
}
|
|
}
|