“Part”, because it is not possible to add or actually manage any booking yet, but it has the export feature that we need to validate the project.
275 lines
7.4 KiB
Go
275 lines
7.4 KiB
Go
/*
|
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
package booking
|
|
|
|
import (
|
|
"context"
|
|
"golang.org/x/text/language"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
|
"dev.tandem.ws/tandem/camper/pkg/form"
|
|
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
|
"dev.tandem.ws/tandem/camper/pkg/locale"
|
|
"dev.tandem.ws/tandem/camper/pkg/template"
|
|
)
|
|
|
|
type AdminHandler struct {
|
|
}
|
|
|
|
func NewAdminHandler() *AdminHandler {
|
|
return &AdminHandler{}
|
|
}
|
|
|
|
func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var head string
|
|
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
|
|
|
switch head {
|
|
case "":
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
serveBookingIndex(w, r, user, company, conn)
|
|
default:
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet)
|
|
}
|
|
case "payment":
|
|
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
|
switch head {
|
|
case "":
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
f := newPaymentForm(user.Locale)
|
|
if err := f.FillFromDatabase(r.Context(), company, conn); err != nil {
|
|
if !database.ErrorIsNotFound(err) {
|
|
panic(err)
|
|
}
|
|
}
|
|
f.MustRender(w, r, user, company)
|
|
case http.MethodPut:
|
|
updatePaymentSettings(w, r, user, company, conn)
|
|
default:
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
|
}
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
})
|
|
}
|
|
|
|
func serveBookingIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
|
bookings, err := collectBookingEntries(r.Context(), conn, user.Locale.Language)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
page := bookingIndex(bookings)
|
|
page.MustRender(w, r, user, company)
|
|
}
|
|
|
|
func collectBookingEntries(ctx context.Context, conn *database.Conn, lang language.Tag) ([]*bookingEntry, error) {
|
|
rows, err := conn.Query(ctx, `
|
|
select left(slug::text, 10)
|
|
, '/admin/booking/' || slug
|
|
, arrival_date
|
|
, departure_date
|
|
, holder_name
|
|
, booking.booking_status
|
|
, coalesce(i18n.name, status.name)
|
|
from booking
|
|
join booking_status as status using (booking_status)
|
|
left join booking_status_i18n as i18n on status.booking_status = i18n.booking_status and i18n.lang_tag = $1
|
|
order by arrival_date desc
|
|
`, lang)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var entries []*bookingEntry
|
|
for rows.Next() {
|
|
entry := &bookingEntry{}
|
|
if err = rows.Scan(&entry.Reference, &entry.URL, &entry.ArrivalDate, &entry.DepartureDate, &entry.HolderName, &entry.Status, &entry.StatusLabel); err != nil {
|
|
return nil, err
|
|
}
|
|
entries = append(entries, entry)
|
|
}
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
type bookingEntry struct {
|
|
Reference string
|
|
URL string
|
|
ArrivalDate time.Time
|
|
DepartureDate time.Time
|
|
HolderName string
|
|
Status string
|
|
StatusLabel string
|
|
}
|
|
|
|
type bookingIndex []*bookingEntry
|
|
|
|
func (page bookingIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
|
switch r.URL.Query().Get("format") {
|
|
case "ods":
|
|
columns := []string{
|
|
"Reference",
|
|
"Arrival Date",
|
|
"Departure Date",
|
|
"Holder Name",
|
|
"Status",
|
|
}
|
|
ods, err := writeTableOds(page, columns, user.Locale, func(sb *strings.Builder, entry *bookingEntry) error {
|
|
if err := writeCellString(sb, entry.Reference); err != nil {
|
|
return err
|
|
}
|
|
writeCellDate(sb, entry.ArrivalDate)
|
|
writeCellDate(sb, entry.DepartureDate)
|
|
if err := writeCellString(sb, entry.HolderName); err != nil {
|
|
return err
|
|
}
|
|
if err := writeCellString(sb, entry.StatusLabel); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
mustWriteOdsResponse(w, ods, user.Locale.Pgettext("bookings.ods", "filename"))
|
|
default:
|
|
template.MustRenderAdmin(w, r, user, company, "booking/index.gohtml", page)
|
|
}
|
|
}
|
|
|
|
type paymentForm struct {
|
|
MerchantCode *form.Input
|
|
TerminalNumber *form.Input
|
|
Environment *form.Select
|
|
Integration *form.Select
|
|
EncryptKey *form.Input
|
|
}
|
|
|
|
func newPaymentForm(l *locale.Locale) *paymentForm {
|
|
return &paymentForm{
|
|
|
|
MerchantCode: &form.Input{
|
|
Name: "merchant_code",
|
|
},
|
|
TerminalNumber: &form.Input{
|
|
Name: "terminal_number",
|
|
},
|
|
Environment: &form.Select{
|
|
Name: "environment",
|
|
Options: []*form.Option{
|
|
{
|
|
Value: "test",
|
|
Label: l.Pgettext("Test", "redsys environment"),
|
|
},
|
|
{
|
|
Value: "live",
|
|
Label: l.Pgettext("Live", "redsys environment"),
|
|
},
|
|
},
|
|
},
|
|
Integration: &form.Select{
|
|
Name: "integration",
|
|
Options: []*form.Option{
|
|
{
|
|
Value: "insite",
|
|
Label: l.Pgettext("InSite", "redsys integration"),
|
|
},
|
|
{
|
|
Value: "redirect",
|
|
Label: l.Pgettext("Redirect", "redsys integration"),
|
|
},
|
|
},
|
|
},
|
|
EncryptKey: &form.Input{
|
|
Name: "encrypt_key",
|
|
},
|
|
}
|
|
}
|
|
|
|
func (f *paymentForm) FillFromDatabase(ctx context.Context, company *auth.Company, conn *database.Conn) error {
|
|
return conn.QueryRow(ctx, `
|
|
select merchant_code
|
|
, terminal_number::text
|
|
, array[environment::text]
|
|
, array[integration::text]
|
|
from redsys
|
|
where company_id = $1`, company.ID).Scan(
|
|
&f.MerchantCode.Val,
|
|
&f.TerminalNumber.Val,
|
|
&f.Environment.Selected,
|
|
&f.Integration.Selected,
|
|
)
|
|
}
|
|
|
|
func (f *paymentForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
|
template.MustRenderAdmin(w, r, user, company, "booking/payment.gohtml", f)
|
|
}
|
|
|
|
func (f *paymentForm) Parse(r *http.Request) error {
|
|
if err := r.ParseForm(); err != nil {
|
|
return err
|
|
}
|
|
f.MerchantCode.FillValue(r)
|
|
f.TerminalNumber.FillValue(r)
|
|
f.Environment.FillValue(r)
|
|
f.Integration.FillValue(r)
|
|
f.EncryptKey.FillValue(r)
|
|
return nil
|
|
}
|
|
|
|
func (f *paymentForm) Valid(l *locale.Locale) bool {
|
|
v := form.NewValidator(l)
|
|
|
|
if v.CheckRequired(f.MerchantCode, l.GettextNoop("Merchant code can not be empty.")) {
|
|
if v.CheckExactLength(f.MerchantCode, 9, l.GettextNoop("Merchant code must be exactly nine digits long.")) {
|
|
v.CheckValidInteger(f.MerchantCode, l.GettextNoop("Merchant code must be a number."))
|
|
}
|
|
}
|
|
|
|
if v.CheckRequired(f.TerminalNumber, l.GettextNoop("Terminal number can not be empty.")) {
|
|
message := l.GettextNoop("Terminal number must be a number between 1 and 999.")
|
|
if v.CheckValidInteger(f.TerminalNumber, message) {
|
|
if v.CheckMinInteger(f.TerminalNumber, 1, message) {
|
|
v.CheckMaxInteger(f.TerminalNumber, 999, message)
|
|
}
|
|
}
|
|
}
|
|
|
|
v.CheckSelectedOptions(f.Environment, l.GettextNoop("Selected environment is not valid."))
|
|
v.CheckSelectedOptions(f.Integration, l.GettextNoop("Selected integration is not valid."))
|
|
|
|
if f.EncryptKey.Val != "" {
|
|
v.CheckValidBase64(f.EncryptKey, l.GettextNoop("The merchant key is not valid."))
|
|
}
|
|
|
|
return v.AllOK
|
|
}
|
|
func updatePaymentSettings(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
|
f := newPaymentForm(user.Locale)
|
|
if ok, err := form.Handle(f, w, r, user); err != nil {
|
|
return
|
|
} else if !ok {
|
|
f.MustRender(w, r, user, company)
|
|
return
|
|
}
|
|
if err := conn.SetupRedsys(r.Context(), company.ID, f.MerchantCode.Val, f.TerminalNumber.Int(), f.Environment.Selected[0], f.Integration.Selected[0], f.EncryptKey.Val); err != nil {
|
|
panic(err)
|
|
}
|
|
httplib.Redirect(w, r, "/admin/booking/payment", http.StatusSeeOther)
|
|
}
|