2024-04-26 15:09:36 +00:00
package booking
import (
"context"
"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"
"github.com/jackc/pgx/v4"
"math"
"net/http"
"time"
)
func serveCheckInForm ( w http . ResponseWriter , r * http . Request , user * auth . User , company * auth . Company , conn * database . Conn , slug string ) {
f := newCheckinForm ( slug )
if err := f . FillFromDatabase ( r . Context ( ) , conn , user . Locale ) ; err != nil {
panic ( err )
}
f . MustRender ( w , r , user , company )
}
func serveGuestForm ( w http . ResponseWriter , r * http . Request , user * auth . User , company * auth . Company , conn * database . Conn , slug string ) {
f , err := newGuestForm ( r . Context ( ) , conn , user . Locale , slug )
if err != nil {
panic ( err )
}
f . MustRender ( w , r , user , company )
}
func checkInBooking ( w http . ResponseWriter , r * http . Request , user * auth . User , company * auth . Company , conn * database . Conn , slug string ) {
f := newCheckinForm ( slug )
if err := f . Parse ( r , user , conn ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
if err := user . VerifyCSRFToken ( r ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusForbidden )
return
}
if ok , err := f . Valid ( r . Context ( ) , conn , user . Locale ) ; err != nil {
panic ( err )
} else if ! ok {
if ! httplib . IsHTMxRequest ( r ) {
w . WriteHeader ( http . StatusUnprocessableEntity )
}
f . MustRender ( w , r , user , company )
}
guests := make ( [ ] * database . CheckedInGuest , 0 , len ( f . Guests ) )
for _ , g := range f . Guests {
guests = append ( guests , g . checkedInGuest ( ) )
}
if err := conn . CheckInGuests ( r . Context ( ) , f . Slug , guests ) ; err != nil {
panic ( err )
}
httplib . Redirect ( w , r , "/admin/bookings" , http . StatusSeeOther )
}
type checkInForm struct {
Slug string
Guests [ ] * guestForm
}
func newCheckinForm ( slug string ) * checkInForm {
return & checkInForm {
Slug : slug ,
}
}
func ( f * checkInForm ) FillFromDatabase ( ctx context . Context , conn * database . Conn , l * locale . Locale ) error {
2024-04-28 18:28:45 +00:00
documentTypes := form . MustGetDocumentTypeOptions ( ctx , conn , l )
2024-04-26 15:09:36 +00:00
sexes := mustGetSexOptions ( ctx , conn , l )
2024-04-28 18:28:45 +00:00
countries := form . MustGetCountryOptions ( ctx , conn , l )
2024-04-26 15:09:36 +00:00
rows , err := conn . Query ( ctx , `
select array [ id_document_type_id ]
, id_document_number
, coalesce ( id_document_issue_date : : text , ' ' )
, given_name
, first_surname
, second_surname
, array [ sex_id ]
, birthdate : : text
, array [ guest . country_code : : text ]
, coalesce ( guest . phone : : text , ' ' )
, guest . address
from booking_guest as guest
join booking using ( booking_id )
where slug = $ 1
` , f . Slug )
if err != nil {
return err
}
defer rows . Close ( )
for rows . Next ( ) {
2024-04-29 15:49:38 +00:00
guest := newGuestFormWithOptions ( documentTypes , sexes , countries , "" , nil )
2024-04-26 15:09:36 +00:00
if err := guest . FillFromRow ( rows ) ; err != nil {
return err
}
f . Guests = append ( f . Guests , guest )
}
if len ( f . Guests ) == 0 {
var numberGuests int
2024-04-29 15:49:38 +00:00
var address string
2024-04-26 15:09:36 +00:00
var country [ ] string
2024-04-29 15:49:38 +00:00
row := conn . QueryRow ( ctx , "select number_adults + number_teenagers, coalesce(address, ''), array[coalesce(country_code, '')] from booking where slug = $1" , f . Slug )
if err = row . Scan ( & numberGuests , & address , & country ) ; err != nil {
2024-04-26 15:09:36 +00:00
return err
}
guests := make ( [ ] * guestForm , 0 , numberGuests )
for i := 0 ; i < numberGuests ; i ++ {
2024-04-29 15:49:38 +00:00
guests = append ( guests , newGuestFormWithOptions ( documentTypes , sexes , countries , address , country ) )
2024-04-26 15:09:36 +00:00
}
f . Guests = guests
}
return nil
}
func mustGetSexOptions ( ctx context . Context , conn * database . Conn , l * locale . Locale ) [ ] * form . Option {
return form . MustGetOptions ( ctx , conn , "select sex.sex_id::text, coalesce(i18n.name, sex.name) as l10n_name from sex left join sex_i18n as i18n on sex.sex_id = i18n.sex_id and i18n.lang_tag = $1 order by l10n_name" , l . Language )
}
func ( f * checkInForm ) Parse ( r * http . Request , user * auth . User , conn * database . Conn ) error {
if err := r . ParseForm ( ) ; err != nil {
return err
}
2024-04-28 18:28:45 +00:00
documentTypes := form . MustGetDocumentTypeOptions ( r . Context ( ) , conn , user . Locale )
2024-04-26 15:09:36 +00:00
sexes := mustGetSexOptions ( r . Context ( ) , conn , user . Locale )
2024-04-28 18:28:45 +00:00
countries := form . MustGetCountryOptions ( r . Context ( ) , conn , user . Locale )
2024-04-26 15:09:36 +00:00
2024-04-29 15:49:38 +00:00
guest := newGuestFormWithOptions ( documentTypes , sexes , countries , "" , nil )
2024-04-26 15:09:36 +00:00
count := guest . count ( r )
f . Guests = make ( [ ] * guestForm , 0 , count )
guest . FillValueIndex ( r , 0 )
f . Guests = append ( f . Guests , guest )
for i := 1 ; i < count ; i ++ {
2024-04-29 15:49:38 +00:00
guest = newGuestFormWithOptions ( documentTypes , sexes , countries , "" , nil )
2024-04-26 15:09:36 +00:00
guest . FillValueIndex ( r , i )
f . Guests = append ( f . Guests , guest )
}
return nil
}
func ( f * checkInForm ) Valid ( ctx context . Context , conn * database . Conn , l * locale . Locale ) ( bool , error ) {
allOK := true
for _ , g := range f . Guests {
if ok , err := g . Valid ( ctx , conn , l ) ; err != nil {
return false , err
} else if ! ok {
allOK = false
}
}
return allOK , nil
}
func ( f * checkInForm ) MustRender ( w http . ResponseWriter , r * http . Request , user * auth . User , company * auth . Company ) {
template . MustRenderAdminFiles ( w , r , user , company , f , "booking/checkin.gohtml" , "booking/guest.gohtml" )
}
type guestForm struct {
IDDocumentType * form . Select
IDDocumentNumber * form . Input
IDDocumentDate * form . Input
GivenName * form . Input
FirstSurname * form . Input
SecondSurname * form . Input
Sex * form . Select
Birthdate * form . Input
Country * form . Select
Address * form . Input
Phone * form . Input
}
func newGuestForm ( ctx context . Context , conn * database . Conn , l * locale . Locale , slug string ) ( * guestForm , error ) {
2024-04-28 18:28:45 +00:00
documentTypes := form . MustGetDocumentTypeOptions ( ctx , conn , l )
2024-04-26 15:09:36 +00:00
sexes := mustGetSexOptions ( ctx , conn , l )
2024-04-28 18:28:45 +00:00
countries := form . MustGetCountryOptions ( ctx , conn , l )
2024-04-26 15:09:36 +00:00
2024-04-29 15:49:38 +00:00
var address string
2024-04-26 15:09:36 +00:00
var country [ ] string
2024-04-29 15:49:38 +00:00
row := conn . QueryRow ( ctx , "select coalesce(address, ''), array[coalesce(country_code, '')] from booking where slug = $1" , slug )
if err := row . Scan ( & address , & country ) ; err != nil {
2024-04-26 15:09:36 +00:00
return nil , err
}
2024-04-29 15:49:38 +00:00
return newGuestFormWithOptions ( documentTypes , sexes , countries , address , country ) , nil
2024-04-26 15:09:36 +00:00
}
2024-04-29 15:49:38 +00:00
func newGuestFormWithOptions ( documentTypes [ ] * form . Option , sexes [ ] * form . Option , countries [ ] * form . Option , address string , selectedCountry [ ] string ) * guestForm {
2024-04-26 15:09:36 +00:00
return & guestForm {
IDDocumentType : & form . Select {
Name : "id_document_type" ,
Options : documentTypes ,
} ,
IDDocumentNumber : & form . Input {
Name : "id_document_number" ,
} ,
IDDocumentDate : & form . Input {
Name : "id_document_date" ,
} ,
GivenName : & form . Input {
Name : "given_name" ,
} ,
FirstSurname : & form . Input {
Name : "first_surname" ,
} ,
SecondSurname : & form . Input {
Name : "second_surname" ,
} ,
Sex : & form . Select {
Name : "sex" ,
Options : sexes ,
} ,
Birthdate : & form . Input {
Name : "birthdate" ,
} ,
Country : & form . Select {
Name : "country" ,
Options : countries ,
Selected : selectedCountry ,
} ,
Address : & form . Input {
Name : "address" ,
2024-04-29 15:49:38 +00:00
Val : address ,
2024-04-26 15:09:36 +00:00
} ,
Phone : & form . Input {
Name : "phone" ,
} ,
}
}
func ( f * guestForm ) count ( r * http . Request ) int {
keys := [ ] string { f . IDDocumentType . Name , f . IDDocumentNumber . Name , f . IDDocumentDate . Name , f . GivenName . Name , f . FirstSurname . Name , f . SecondSurname . Name , f . Sex . Name , f . Birthdate . Name , f . Country . Name , f . Address . Name , f . Phone . Name }
min := math . MaxInt
for _ , key := range keys {
l := len ( r . Form [ key ] )
if len ( r . Form [ key ] ) < min {
min = l
}
}
return min
}
func ( f * guestForm ) FillValueIndex ( r * http . Request , idx int ) {
f . IDDocumentType . FillValueIndex ( r , idx )
f . IDDocumentNumber . FillValueIndex ( r , idx )
f . IDDocumentDate . FillValueIndex ( r , idx )
f . GivenName . FillValueIndex ( r , idx )
f . FirstSurname . FillValueIndex ( r , idx )
f . SecondSurname . FillValueIndex ( r , idx )
f . Sex . FillValueIndex ( r , idx )
f . Birthdate . FillValueIndex ( r , idx )
f . Country . FillValueIndex ( r , idx )
f . Address . FillValueIndex ( r , idx )
f . Phone . FillValueIndex ( r , idx )
}
func ( f * guestForm ) FillFromRow ( row pgx . Rows ) error {
return row . Scan (
& f . IDDocumentType . Selected ,
& f . IDDocumentNumber . Val ,
& f . IDDocumentDate . Val ,
& f . GivenName . Val ,
& f . FirstSurname . Val ,
& f . SecondSurname . Val ,
& f . Sex . Selected ,
& f . Birthdate . Val ,
& f . Country . Selected ,
& f . Phone . Val ,
& f . Address . Val ,
)
}
func ( f * guestForm ) Valid ( ctx context . Context , conn * database . Conn , l * locale . Locale ) ( bool , error ) {
v := form . NewValidator ( l )
today := time . Now ( )
yesterday := time . Date ( today . Year ( ) , today . Month ( ) , today . Day ( ) , 0 , 0 , 0 , 0 , time . UTC )
v . CheckSelectedOptions ( f . IDDocumentType , l . GettextNoop ( "Selected ID document type is not valid." ) )
v . CheckRequired ( f . IDDocumentNumber , l . GettextNoop ( "ID document number can not be empty." ) )
if f . IDDocumentDate . Val != "" {
if v . CheckValidDate ( f . IDDocumentDate , l . GettextNoop ( "ID document issue date must be a valid date." ) ) {
v . CheckMaxDate ( f . IDDocumentDate , yesterday , l . Gettext ( "ID document issue date must be in the past." ) )
}
}
v . CheckRequired ( f . GivenName , l . GettextNoop ( "Full name can not be empty." ) )
v . CheckRequired ( f . FirstSurname , l . GettextNoop ( "Full name can not be empty." ) )
v . CheckSelectedOptions ( f . Sex , l . GettextNoop ( "Selected sex is not valid." ) )
if v . CheckRequired ( f . Birthdate , l . GettextNoop ( "Birthdate can not be empty" ) ) {
if v . CheckValidDate ( f . Birthdate , l . GettextNoop ( "Birthdate must be a valid date." ) ) {
v . CheckMaxDate ( f . Birthdate , yesterday , l . Gettext ( "Birthdate must be in the past." ) )
}
}
var country string
if v . CheckSelectedOptions ( f . Country , l . GettextNoop ( "Selected country is not valid." ) ) {
country = f . Country . Selected [ 0 ]
}
if f . Phone . Val != "" && country != "" {
if _ , err := v . CheckValidPhone ( ctx , conn , f . Phone , country , l . GettextNoop ( "This phone number is not valid." ) ) ; err != nil {
return false , err
}
}
return v . AllOK , nil
}
func ( f * guestForm ) checkedInGuest ( ) * database . CheckedInGuest {
birthdate , err := time . Parse ( database . ISODateFormat , f . Birthdate . Val )
if err != nil {
panic ( err )
}
issueDate , err := time . Parse ( database . ISODateFormat , f . IDDocumentDate . Val )
if err != nil {
issueDate = time . Time { }
}
return & database . CheckedInGuest {
IDDocumentType : f . IDDocumentType . String ( ) ,
IDDocumentNumber : f . IDDocumentNumber . Val ,
IDDocumentIssueDate : issueDate ,
GivenName : f . GivenName . Val ,
FirstSurname : f . FirstSurname . Val ,
SecondSurname : f . SecondSurname . Val ,
Sex : f . Sex . String ( ) ,
Birthdate : birthdate ,
CountryCode : f . Country . String ( ) ,
Phone : f . Phone . Val ,
Address : f . Address . Val ,
}
}
func ( f * guestForm ) MustRender ( w http . ResponseWriter , r * http . Request , user * auth . User , company * auth . Company ) {
template . MustRenderAdminNoLayout ( w , r , user , company , "booking/guest.gohtml" , f )
}