Compare commits

...

10 Commits

Author SHA1 Message Date
jordi fita mas 07705b012a Add a very ugly login page to test database connection
I want to perform all SQL queries in a thread, to avoid freezing the UI,
that sometimes might happen when there is a lot of data to fetch; should
not happen very often, though.

Neither libpq nor Qt SQL allow queries on the same connection from
differents threads, and, in Qt SQL, all queries must be performed from
the same thread where the connection was established.  In Qt5 i had to
either create a connection per thread, or use a QThread-derived object
to hold the connection and use signals and slots to pass query and
response data between the UI and database threads; it was usable but not
pretty.

With Qt6 and Concurrent’s QThreadPool now i can use QFutures instead,
that are not as cumbersome as with Qt5, because i no longer need
QFutureWatcher.  I still have the problem that all queries must be done
from within the same thread, and QThreadPool uses an arbitrary thread.
The solution is to create a “pool” with a single, non-expirable thread,
and call all Concurrent::run onto that pool.

I have to test it properly, and first need to open the database to test
whether that, at least, works. I added a simple “login page” for that,
and to make a first attempt to error messages; i use a control that is
like Kirigami’s InlineMessage for now, but i am not sure.

I also do not know how i will configure database’s connection details. I
usually make use of pg_service.conf, because then the application only
need to know its service name, but i am not sure whether other people
would find it as comfortable as i do.
2024-12-16 12:59:19 +01:00
jordi fita mas 49b2c035ad Add skeleton for a QML application 2024-12-14 01:19:20 +01:00
jordi fita mas 7c6bac1986 Add season dates for “next year”
This is to test the booking form’s behavior when there is a gap between
bookable dates, especially around New Year’s.
2024-11-20 19:43:59 +01:00
jordi fita mas 5b89c97b00 Add operating_dates to campsite type table
We forgot that different accommodation types are not always operating on
the whole season calendar, thus we need a specific date for each type.

Someday i will add the field in the administration panel, but for now i
will have to add them by hand, as people are starting to book plots on
dates that are not operating.
2024-07-15 23:41:47 +02:00
jordi fita mas d8524c347e Fix French typo 2024-07-15 23:12:12 +02:00
jordi fita mas c54e147173 Change “ACSI” to “ACSI / ANWB”
Apparently, ANWB is a camping card similar to ACSI from the Netherlands,
and both cards have the exact same discounts.
2024-05-13 10:40:21 +02:00
jordi fita mas 92c0cb4de0 Add filters and pagination to login attempts 2024-05-03 20:45:14 +02:00
jordi fita mas b4ccdeff2f Add filters and pagination to payments 2024-05-03 20:09:07 +02:00
jordi fita mas 48c1529e6c Add pagination to invoices
I’ve removed the total amount because it is very difficult to get it
with pagination, and customer never saw it (it was from Numerus), thus
they won’t miss it—i hope.
2024-05-03 19:13:49 +02:00
jordi fita mas 674cdff87b Add a new Cursor form type
To hold the common logic of detecting pagination, forming the key, and
splitting its values later on.

I can take advantage that a form with action="get" already adds its
fields to the query string to have a common template for pagination. The
only problem is that i have different column spans for different tables,
therefore had to add a colspan to the struct.
2024-05-03 19:00:02 +02:00
45 changed files with 1550 additions and 689 deletions

3
.gitignore vendored
View File

@ -1,4 +1,7 @@
/.idea/ /.idea/
/build/
/locale/ /locale/
/po/*.pot /po/*.pot
/demo.sql /demo.sql
CMakeLists.txt.user*
.qmlls.ini

18
CMakeLists.txt Normal file
View File

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.16)
project(camper VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS
Concurrent
Quick
QuickControls2
Sql
)
qt_standard_project_setup(REQUIRES 6.5)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(src)

View File

@ -1248,6 +1248,15 @@ select set_season_range(93, daterange(make_date(extract(year from current_date):
select set_season_range(94, daterange(make_date(extract(year from current_date)::int, 9, 24), make_date(extract(year from current_date)::int, 9, 29))); select set_season_range(94, daterange(make_date(extract(year from current_date)::int, 9, 24), make_date(extract(year from current_date)::int, 9, 29)));
select set_season_range(93, daterange(make_date(extract(year from current_date)::int, 9, 29), make_date(extract(year from current_date)::int, 10, 1))); select set_season_range(93, daterange(make_date(extract(year from current_date)::int, 9, 29), make_date(extract(year from current_date)::int, 10, 1)));
select set_season_range(94, daterange(make_date(extract(year from current_date)::int, 10, 1), make_date(extract(year from current_date)::int, 10, 13))); select set_season_range(94, daterange(make_date(extract(year from current_date)::int, 10, 1), make_date(extract(year from current_date)::int, 10, 13)));
select set_season_range(92, daterange(make_date(extract(year from current_date)::int + 1, 4, 11), make_date(extract(year from current_date)::int + 1, 4, 22)));
select set_season_range(94, daterange(make_date(extract(year from current_date)::int + 1, 4, 22), make_date(extract(year from current_date)::int + 1, 6, 20)));
select set_season_range(92, daterange(make_date(extract(year from current_date)::int + 1, 6, 20), make_date(extract(year from current_date)::int + 1, 6, 25)));
select set_season_range(93, daterange(make_date(extract(year from current_date)::int + 1, 6, 25), make_date(extract(year from current_date)::int + 1, 7, 4)));
select set_season_range(92, daterange(make_date(extract(year from current_date)::int + 1, 7, 4), make_date(extract(year from current_date)::int + 1, 8, 25)));
select set_season_range(93, daterange(make_date(extract(year from current_date)::int + 1, 8, 25), make_date(extract(year from current_date)::int + 1, 9, 1)));
select set_season_range(94, daterange(make_date(extract(year from current_date)::int + 1, 9, 1), make_date(extract(year from current_date)::int + 1, 9, 11)));
select set_season_range(92, daterange(make_date(extract(year from current_date)::int + 1, 9, 11), make_date(extract(year from current_date)::int + 1, 9, 15)));
select set_season_range(94, daterange(make_date(extract(year from current_date)::int + 1, 9, 15), make_date(extract(year from current_date)::int + 1, 12, 9)));
select set_campsite_type_cost (slug, 92, '4.00', '7.95', '7.95', '6.40') from campsite_type where campsite_type_id = 72; select set_campsite_type_cost (slug, 92, '4.00', '7.95', '7.95', '6.40') from campsite_type where campsite_type_id = 72;
select set_campsite_type_cost (slug, 93, '2.00', '7.40', '7.40', '5.90') from campsite_type where campsite_type_id = 72; select set_campsite_type_cost (slug, 93, '2.00', '7.40', '7.40', '5.90') from campsite_type where campsite_type_id = 72;

View File

@ -0,0 +1,12 @@
-- Deploy camper:campsite_type__operating_dates to pg
-- requires: campsite_type
begin;
set search_path to camper, public;
alter table campsite_type
add column operating_dates daterange not null default 'empty'
;
commit;

View File

@ -172,7 +172,7 @@ func collectBookingEntries(ctx context.Context, conn *database.Conn, lang langua
order by lower(stay) desc order by lower(stay) desc
, booking_id desc , booking_id desc
LIMIT %d LIMIT %d
`, where, filters.perPage+1), args...) `, where, filters.PerPage()+1), args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -235,7 +235,7 @@ func (page bookingIndex) MustRender(w http.ResponseWriter, r *http.Request, user
} }
ods.MustWriteResponse(w, table, user.Locale.Pgettext("bookings.ods", "filename")) ods.MustWriteResponse(w, table, user.Locale.Pgettext("bookings.ods", "filename"))
default: default:
if httplib.IsHTMxRequest(r) && page.Filters.pagination { if httplib.IsHTMxRequest(r) && page.Filters.Paginated() {
template.MustRenderAdminNoLayout(w, r, user, company, "booking/results.gohtml", page) template.MustRenderAdminNoLayout(w, r, user, company, "booking/results.gohtml", page)
} else { } else {
template.MustRenderAdminFiles(w, r, user, company, page, "booking/index.gohtml", "booking/results.gohtml") template.MustRenderAdminFiles(w, r, user, company, page, "booking/index.gohtml", "booking/results.gohtml")

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"strings" "strings"
"dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/auth"
@ -13,22 +14,17 @@ import (
) )
type filterForm struct { type filterForm struct {
locale *locale.Locale
company *auth.Company company *auth.Company
perPage int
pagination bool
HolderName *form.Input HolderName *form.Input
BookingStatus *form.Select BookingStatus *form.Select
FromDate *form.Input FromDate *form.Input
ToDate *form.Input ToDate *form.Input
Cursor *form.Input Cursor *form.Cursor
} }
func newFilterForm(ctx context.Context, conn *database.Conn, company *auth.Company, locale *locale.Locale) *filterForm { func newFilterForm(ctx context.Context, conn *database.Conn, company *auth.Company, locale *locale.Locale) *filterForm {
return &filterForm{ return &filterForm{
locale: locale,
company: company, company: company,
perPage: 25,
HolderName: &form.Input{ HolderName: &form.Input{
Name: "holder_name", Name: "holder_name",
}, },
@ -42,8 +38,9 @@ func newFilterForm(ctx context.Context, conn *database.Conn, company *auth.Compa
ToDate: &form.Input{ ToDate: &form.Input{
Name: "to_date", Name: "to_date",
}, },
Cursor: &form.Input{ Cursor: &form.Cursor{
Name: "cursor", Name: "cursor",
PerPage: 25,
}, },
} }
} }
@ -68,7 +65,6 @@ func (f *filterForm) Parse(r *http.Request) error {
f.FromDate.FillValue(r) f.FromDate.FillValue(r)
f.ToDate.FillValue(r) f.ToDate.FillValue(r)
f.Cursor.FillValue(r) f.Cursor.FillValue(r)
f.pagination = f.Cursor.Val != ""
return nil return nil
} }
@ -100,8 +96,8 @@ func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) {
maybeAppendWhere("lower(stay) >= $%d", f.FromDate.Val, nil) maybeAppendWhere("lower(stay) >= $%d", f.FromDate.Val, nil)
maybeAppendWhere("lower(stay) <= $%d", f.ToDate.Val, nil) maybeAppendWhere("lower(stay) <= $%d", f.ToDate.Val, nil)
if f.Cursor.Val != "" { if f.Paginated() {
params := strings.Split(f.Cursor.Val, ";") params := f.Cursor.Params()
if len(params) == 2 { if len(params) == 2 {
where = append(where, fmt.Sprintf("(lower(stay), booking_id) < ($%d, $%d)", len(args)+1, len(args)+2)) where = append(where, fmt.Sprintf("(lower(stay), booking_id) < ($%d, $%d)", len(args)+1, len(args)+2))
args = append(args, params[0]) args = append(args, params[0])
@ -113,14 +109,9 @@ func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) {
} }
func (f *filterForm) buildCursor(bookings []*bookingEntry) []*bookingEntry { func (f *filterForm) buildCursor(bookings []*bookingEntry) []*bookingEntry {
if len(bookings) <= f.perPage { return form.BuildCursor(f.Cursor, bookings, func(entry *bookingEntry) []string {
f.Cursor.Val = "" return []string{entry.ArrivalDate.Format(database.ISODateFormat), strconv.Itoa(entry.ID)}
return bookings })
}
bookings = bookings[:f.perPage]
last := bookings[f.perPage-1]
f.Cursor.Val = fmt.Sprintf("%s;%d", last.ArrivalDate.Format(database.ISODateFormat), last.ID)
return bookings
} }
func (f *filterForm) HasValue() bool { func (f *filterForm) HasValue() bool {
@ -129,3 +120,11 @@ func (f *filterForm) HasValue() bool {
f.FromDate.Val != "" || f.FromDate.Val != "" ||
f.ToDate.Val != "" f.ToDate.Val != ""
} }
func (f *filterForm) PerPage() int {
return f.Cursor.PerPage
}
func (f *filterForm) Paginated() bool {
return f.Cursor.Pagination
}

View File

@ -59,7 +59,7 @@ type prebookingIndex struct {
} }
func (page prebookingIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { func (page prebookingIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
if httplib.IsHTMxRequest(r) && page.Filters.pagination { if httplib.IsHTMxRequest(r) && page.Filters.Paginated() {
template.MustRenderAdminNoLayout(w, r, user, company, "prebooking/results.gohtml", page) template.MustRenderAdminNoLayout(w, r, user, company, "prebooking/results.gohtml", page)
} else { } else {
template.MustRenderAdminFiles(w, r, user, company, page, "prebooking/index.gohtml", "prebooking/results.gohtml") template.MustRenderAdminFiles(w, r, user, company, page, "prebooking/index.gohtml", "prebooking/results.gohtml")

View File

@ -245,14 +245,14 @@ func NewDateFields(ctx context.Context, conn *database.Conn, campsiteType string
row := conn.QueryRow(ctx, ` row := conn.QueryRow(ctx, `
select lower(bookable_nights), select lower(bookable_nights),
upper(bookable_nights) - 1, upper(bookable_nights) - 1,
greatest(min(lower(season_range)), current_timestamp::date), greatest(min(lower(season_range)), lower(operating_dates), current_timestamp::date),
max(upper(season_range)) least(max(upper(season_range)), upper(operating_dates))
from campsite_type from campsite_type
join campsite_type_cost using (campsite_type_id) join campsite_type_cost using (campsite_type_id)
join season_calendar using (season_id) join season_calendar using (season_id)
where campsite_type.slug = $1 where campsite_type.slug = $1
and season_range >> daterange(date_trunc('year', current_timestamp)::date, date_trunc('year', current_timestamp)::date + 1) and season_range >> daterange(date_trunc('year', current_timestamp)::date, date_trunc('year', current_timestamp)::date + 1)
group by bookable_nights; group by bookable_nights, operating_dates
`, campsiteType) `, campsiteType)
f := &DateFields{ f := &DateFields{
ArrivalDate: &bookingDateInput{ ArrivalDate: &bookingDateInput{

View File

@ -109,7 +109,7 @@ func collectCustomerEntries(ctx context.Context, conn *database.Conn, company *a
where (%s) where (%s)
order by name, contact_id order by name, contact_id
LIMIT %d LIMIT %d
`, where, filters.perPage+1), args...) `, where, filters.PerPage()+1), args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -141,7 +141,7 @@ type customerIndex struct {
} }
func (page *customerIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { func (page *customerIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
if httplib.IsHTMxRequest(r) && page.Filters.pagination { if httplib.IsHTMxRequest(r) && page.Filters.Paginated() {
template.MustRenderAdminNoLayout(w, r, user, company, "customer/results.gohtml", page) template.MustRenderAdminNoLayout(w, r, user, company, "customer/results.gohtml", page)
} else { } else {
template.MustRenderAdminFiles(w, r, user, company, page, "customer/index.gohtml", "customer/results.gohtml") template.MustRenderAdminFiles(w, r, user, company, page, "customer/index.gohtml", "customer/results.gohtml")

View File

@ -3,6 +3,7 @@ package customer
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"strings" "strings"
"dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/auth"
@ -10,26 +11,24 @@ import (
) )
type filterForm struct { type filterForm struct {
company *auth.Company company *auth.Company
perPage int Name *form.Input
pagination bool Email *form.Input
Name *form.Input Cursor *form.Cursor
Email *form.Input
Cursor *form.Input
} }
func newFilterForm(company *auth.Company) *filterForm { func newFilterForm(company *auth.Company) *filterForm {
return &filterForm{ return &filterForm{
company: company, company: company,
perPage: 25,
Name: &form.Input{ Name: &form.Input{
Name: "name", Name: "name",
}, },
Email: &form.Input{ Email: &form.Input{
Name: "email", Name: "email",
}, },
Cursor: &form.Input{ Cursor: &form.Cursor{
Name: "cursor", Name: "cursor",
PerPage: 25,
}, },
} }
} }
@ -41,7 +40,6 @@ func (f *filterForm) Parse(r *http.Request) error {
f.Name.FillValue(r) f.Name.FillValue(r)
f.Email.FillValue(r) f.Email.FillValue(r)
f.Cursor.FillValue(r) f.Cursor.FillValue(r)
f.pagination = f.Cursor.Val != ""
return nil return nil
} }
@ -69,8 +67,8 @@ func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) {
return "%" + v + "%" return "%" + v + "%"
}) })
if f.Cursor.Val != "" { if f.Paginated() {
params := strings.Split(f.Cursor.Val, ";") params := f.Cursor.Params()
if len(params) == 2 { if len(params) == 2 {
where = append(where, fmt.Sprintf("(name, contact_id) > ($%d, $%d)", len(args)+1, len(args)+2)) where = append(where, fmt.Sprintf("(name, contact_id) > ($%d, $%d)", len(args)+1, len(args)+2))
args = append(args, params[0]) args = append(args, params[0])
@ -82,17 +80,20 @@ func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) {
} }
func (f *filterForm) buildCursor(customers []*customerEntry) []*customerEntry { func (f *filterForm) buildCursor(customers []*customerEntry) []*customerEntry {
if len(customers) <= f.perPage { return form.BuildCursor(f.Cursor, customers, func(entry *customerEntry) []string {
f.Cursor.Val = "" return []string{entry.Name, strconv.Itoa(entry.ID)}
return customers })
}
customers = customers[:f.perPage]
last := customers[f.perPage-1]
f.Cursor.Val = fmt.Sprintf("%s;%d", last.Name, last.ID)
return customers
} }
func (f *filterForm) HasValue() bool { func (f *filterForm) HasValue() bool {
return f.Name.Val != "" || return f.Name.Val != "" ||
f.Email.Val != "" f.Email.Val != ""
} }
func (f *filterForm) PerPage() int {
return f.Cursor.PerPage
}
func (f *filterForm) Paginated() bool {
return f.Cursor.Pagination
}

33
pkg/form/cursor.go Normal file
View File

@ -0,0 +1,33 @@
package form
import (
"net/http"
"strings"
)
type Cursor struct {
PerPage int
Pagination bool
Name string
Val string
Colspan int
}
func (cursor *Cursor) FillValue(r *http.Request) {
cursor.Val = strings.TrimSpace(r.FormValue(cursor.Name))
cursor.Pagination = cursor.Val != ""
}
func (cursor *Cursor) Params() []string {
return strings.Split(cursor.Val, ";")
}
func BuildCursor[K interface{}](cursor *Cursor, elems []K, build func(K) []string) []K {
if len(elems) <= cursor.PerPage {
cursor.Val = ""
return elems
}
elems = elems[:cursor.PerPage]
cursor.Val = strings.Join(build(elems[cursor.PerPage-1]), ";")
return elems
}

View File

@ -148,14 +148,13 @@ type IndexEntry struct {
} }
func serveInvoiceIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { func serveInvoiceIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
filters := newInvoiceFilterForm(r.Context(), conn, company, user.Locale) filters := newFilterForm(r.Context(), conn, company, user.Locale)
if err := filters.Parse(r); err != nil { if err := filters.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
page := &invoiceIndex{ page := &invoiceIndex{
Invoices: mustCollectInvoiceEntries(r.Context(), conn, user.Locale, filters), Invoices: filters.buildCursor(mustCollectInvoiceEntries(r.Context(), conn, user.Locale, filters)),
TotalAmount: mustComputeInvoicesTotalAmount(r.Context(), conn, filters),
Filters: filters, Filters: filters,
InvoiceStatuses: mustCollectInvoiceStatuses(r.Context(), conn, user.Locale), InvoiceStatuses: mustCollectInvoiceStatuses(r.Context(), conn, user.Locale),
} }
@ -164,16 +163,19 @@ func serveInvoiceIndex(w http.ResponseWriter, r *http.Request, user *auth.User,
type invoiceIndex struct { type invoiceIndex struct {
Invoices []*IndexEntry Invoices []*IndexEntry
TotalAmount string Filters *filterForm
Filters *invoiceFilterForm
InvoiceStatuses map[string]string InvoiceStatuses map[string]string
} }
func (page *invoiceIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { func (page *invoiceIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "invoice/index.gohtml", page) if httplib.IsHTMxRequest(r) && page.Filters.Paginated() {
template.MustRenderAdminNoLayout(w, r, user, company, "invoice/results.gohtml", page)
} else {
template.MustRenderAdminFiles(w, r, user, company, page, "invoice/index.gohtml", "invoice/results.gohtml")
}
} }
func mustCollectInvoiceEntries(ctx context.Context, conn *database.Conn, locale *locale.Locale, filters *invoiceFilterForm) []*IndexEntry { func mustCollectInvoiceEntries(ctx context.Context, conn *database.Conn, locale *locale.Locale, filters *filterForm) []*IndexEntry {
where, args := filters.BuildQuery([]interface{}{locale.Language.String()}) where, args := filters.BuildQuery([]interface{}{locale.Language.String()})
rows, err := conn.Query(ctx, fmt.Sprintf(` rows, err := conn.Query(ctx, fmt.Sprintf(`
select invoice_id select invoice_id
@ -192,7 +194,8 @@ func mustCollectInvoiceEntries(ctx context.Context, conn *database.Conn, locale
where (%s) where (%s)
order by invoice_date desc order by invoice_date desc
, invoice_number desc , invoice_number desc
`, where), args...) limit %d
`, where, filters.PerPage()+1), args...)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -213,25 +216,6 @@ func mustCollectInvoiceEntries(ctx context.Context, conn *database.Conn, locale
return entries return entries
} }
func mustComputeInvoicesTotalAmount(ctx context.Context, conn *database.Conn, filters *invoiceFilterForm) string {
where, args := filters.BuildQuery(nil)
text, err := conn.GetText(ctx, fmt.Sprintf(`
select to_price(sum(total)::integer, decimal_digits)
from invoice
join invoice_amount using (invoice_id)
join currency using (currency_code)
where (%s)
group by decimal_digits
`, where), args...)
if err != nil {
if database.ErrorIsNotFound(err) {
return "0.0"
}
panic(err)
}
return text
}
func mustCollectInvoiceStatuses(ctx context.Context, conn *database.Conn, locale *locale.Locale) map[string]string { func mustCollectInvoiceStatuses(ctx context.Context, conn *database.Conn, locale *locale.Locale) map[string]string {
rows, err := conn.Query(ctx, ` rows, err := conn.Query(ctx, `
select invoice_status.invoice_status select invoice_status.invoice_status
@ -261,88 +245,6 @@ func mustCollectInvoiceStatuses(ctx context.Context, conn *database.Conn, locale
return statuses return statuses
} }
type invoiceFilterForm struct {
locale *locale.Locale
company *auth.Company
Customer *form.Select
InvoiceStatus *form.Select
InvoiceNumber *form.Input
FromDate *form.Input
ToDate *form.Input
}
func newInvoiceFilterForm(ctx context.Context, conn *database.Conn, company *auth.Company, locale *locale.Locale) *invoiceFilterForm {
return &invoiceFilterForm{
locale: locale,
company: company,
Customer: &form.Select{
Name: "customer",
Options: mustGetContactOptions(ctx, conn, company),
},
InvoiceStatus: &form.Select{
Name: "invoice_status",
Options: mustGetInvoiceStatusOptions(ctx, conn, locale),
},
InvoiceNumber: &form.Input{
Name: "number",
},
FromDate: &form.Input{
Name: "from_date",
},
ToDate: &form.Input{
Name: "to_date",
},
}
}
func (f *invoiceFilterForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
f.Customer.FillValue(r)
f.InvoiceStatus.FillValue(r)
f.InvoiceNumber.FillValue(r)
f.FromDate.FillValue(r)
f.ToDate.FillValue(r)
return nil
}
func (f *invoiceFilterForm) BuildQuery(args []interface{}) (string, []interface{}) {
var where []string
appendWhere := func(expression string, value interface{}) {
args = append(args, value)
where = append(where, fmt.Sprintf(expression, len(args)))
}
maybeAppendWhere := func(expression string, value string, conv func(string) interface{}) {
if value != "" {
if conv == nil {
appendWhere(expression, value)
} else {
appendWhere(expression, conv(value))
}
}
}
appendWhere("invoice.company_id = $%d", f.company.ID)
maybeAppendWhere("contact_id = $%d", f.Customer.String(), func(v string) interface{} {
customerId, _ := strconv.Atoi(f.Customer.Selected[0])
return customerId
})
maybeAppendWhere("invoice.invoice_status = $%d", f.InvoiceStatus.String(), nil)
maybeAppendWhere("invoice_number = $%d", f.InvoiceNumber.Val, nil)
maybeAppendWhere("invoice_date >= $%d", f.FromDate.Val, nil)
maybeAppendWhere("invoice_date <= $%d", f.ToDate.Val, nil)
return strings.Join(where, ") AND ("), args
}
func (f *invoiceFilterForm) HasValue() bool {
return (len(f.Customer.Selected) > 0 && f.Customer.Selected[0] != "") ||
(len(f.InvoiceStatus.Selected) > 0 && f.InvoiceStatus.Selected[0] != "") ||
f.InvoiceNumber.Val != "" ||
f.FromDate.Val != "" ||
f.ToDate.Val != ""
}
func serveInvoice(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, slug string) { func serveInvoice(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, slug string) {
pdf := false pdf := false
if strings.HasSuffix(slug, ".pdf") { if strings.HasSuffix(slug, ".pdf") {
@ -682,7 +584,7 @@ func handleBatchAction(w http.ResponseWriter, r *http.Request, user *auth.User,
panic(err) panic(err)
} }
case "export": case "export":
filters := newInvoiceFilterForm(r.Context(), conn, company, user.Locale) filters := newFilterForm(r.Context(), conn, company, user.Locale)
if err := filters.Parse(r); err != nil { if err := filters.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return

124
pkg/invoice/filter.go Normal file
View File

@ -0,0 +1,124 @@
package invoice
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/database"
"dev.tandem.ws/tandem/camper/pkg/form"
"dev.tandem.ws/tandem/camper/pkg/locale"
)
type filterForm struct {
company *auth.Company
Customer *form.Select
InvoiceStatus *form.Select
InvoiceNumber *form.Input
FromDate *form.Input
ToDate *form.Input
Cursor *form.Cursor
}
func newFilterForm(ctx context.Context, conn *database.Conn, company *auth.Company, locale *locale.Locale) *filterForm {
return &filterForm{
company: company,
Customer: &form.Select{
Name: "customer",
Options: mustGetContactOptions(ctx, conn, company),
},
InvoiceStatus: &form.Select{
Name: "invoice_status",
Options: mustGetInvoiceStatusOptions(ctx, conn, locale),
},
InvoiceNumber: &form.Input{
Name: "number",
},
FromDate: &form.Input{
Name: "from_date",
},
ToDate: &form.Input{
Name: "to_date",
},
Cursor: &form.Cursor{
Name: "cursor",
PerPage: 25,
},
}
}
func (f *filterForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
f.Customer.FillValue(r)
f.InvoiceStatus.FillValue(r)
f.InvoiceNumber.FillValue(r)
f.FromDate.FillValue(r)
f.ToDate.FillValue(r)
f.Cursor.FillValue(r)
return nil
}
func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) {
var where []string
appendWhere := func(expression string, value interface{}) {
args = append(args, value)
where = append(where, fmt.Sprintf(expression, len(args)))
}
maybeAppendWhere := func(expression string, value string, conv func(string) interface{}) {
if value != "" {
if conv == nil {
appendWhere(expression, value)
} else {
appendWhere(expression, conv(value))
}
}
}
appendWhere("invoice.company_id = $%d", f.company.ID)
maybeAppendWhere("contact_id = $%d", f.Customer.String(), func(v string) interface{} {
customerId, _ := strconv.Atoi(f.Customer.Selected[0])
return customerId
})
maybeAppendWhere("invoice.invoice_status = $%d", f.InvoiceStatus.String(), nil)
maybeAppendWhere("invoice_number = $%d", f.InvoiceNumber.Val, nil)
maybeAppendWhere("invoice_date >= $%d", f.FromDate.Val, nil)
maybeAppendWhere("invoice_date <= $%d", f.ToDate.Val, nil)
if f.Paginated() {
params := f.Cursor.Params()
if len(params) == 2 {
where = append(where, fmt.Sprintf("(invoice_date, invoice_number) < ($%d, $%d)", len(args)+1, len(args)+2))
args = append(args, params[0])
args = append(args, params[1])
}
}
return strings.Join(where, ") AND ("), args
}
func (f *filterForm) buildCursor(customers []*IndexEntry) []*IndexEntry {
return form.BuildCursor(f.Cursor, customers, func(entry *IndexEntry) []string {
return []string{entry.Date.Format(database.ISODateFormat), entry.Number}
})
}
func (f *filterForm) HasValue() bool {
return (len(f.Customer.Selected) > 0 && f.Customer.Selected[0] != "") ||
(len(f.InvoiceStatus.Selected) > 0 && f.InvoiceStatus.Selected[0] != "") ||
f.InvoiceNumber.Val != "" ||
f.FromDate.Val != "" ||
f.ToDate.Val != ""
}
func (f *filterForm) PerPage() int {
return f.Cursor.PerPage
}
func (f *filterForm) Paginated() bool {
return f.Cursor.Pagination
}

View File

@ -108,17 +108,24 @@ func (h *AdminHandler) paymentHandler(user *auth.User, company *auth.Company, co
} }
func servePaymentIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { func servePaymentIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
payments, err := collectPaymentEntries(r.Context(), company, conn, user.Locale) filters := newFilterForm(r.Context(), conn, company, user.Locale)
if err := filters.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
payments, err := collectPaymentEntries(r.Context(), company, conn, user.Locale, filters)
if err != nil { if err != nil {
panic(err) panic(err)
} }
page := &paymentIndex{ page := &paymentIndex{
Payments: payments, Payments: filters.buildCursor(payments),
Filters: filters,
} }
page.MustRender(w, r, user, company) page.MustRender(w, r, user, company)
} }
type paymentEntry struct { type paymentEntry struct {
ID int
URL string URL string
Reference string Reference string
DownPayment string DownPayment string
@ -128,9 +135,11 @@ type paymentEntry struct {
CreatedAt time.Time CreatedAt time.Time
} }
func collectPaymentEntries(ctx context.Context, company *auth.Company, conn *database.Conn, locale *locale.Locale) ([]*paymentEntry, error) { func collectPaymentEntries(ctx context.Context, company *auth.Company, conn *database.Conn, locale *locale.Locale, filters *filterForm) ([]*paymentEntry, error) {
rows, err := conn.Query(ctx, ` where, args := filters.BuildQuery([]interface{}{locale.Language})
select '/admin/payments/' || payment.slug rows, err := conn.Query(ctx, fmt.Sprintf(`
select payment_id
, '/admin/payments/' || payment.slug
, payment.reference , payment.reference
, to_price(payment.down_payment, decimal_digits) , to_price(payment.down_payment, decimal_digits)
, to_price(total, decimal_digits) , to_price(total, decimal_digits)
@ -141,10 +150,12 @@ func collectPaymentEntries(ctx context.Context, company *auth.Company, conn *dat
join currency using (currency_code) join currency using (currency_code)
join payment_status using (payment_status) join payment_status using (payment_status)
left join payment_status_i18n on payment_status_i18n.payment_status = payment.payment_status left join payment_status_i18n on payment_status_i18n.payment_status = payment.payment_status
and payment_status_i18n.lang_tag = $2 and payment_status_i18n.lang_tag = $1
where company_id = $1 where (%s)
order by created_at desc order by created_at desc
`, company.ID, locale.Language) , payment_id
limit %d
`, where, filters.PerPage()+1), args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -154,6 +165,7 @@ func collectPaymentEntries(ctx context.Context, company *auth.Company, conn *dat
for rows.Next() { for rows.Next() {
entry := &paymentEntry{} entry := &paymentEntry{}
if err = rows.Scan( if err = rows.Scan(
&entry.ID,
&entry.URL, &entry.URL,
&entry.Reference, &entry.Reference,
&entry.DownPayment, &entry.DownPayment,
@ -172,10 +184,15 @@ func collectPaymentEntries(ctx context.Context, company *auth.Company, conn *dat
type paymentIndex struct { type paymentIndex struct {
Payments []*paymentEntry Payments []*paymentEntry
Filters *filterForm
} }
func (page *paymentIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) { func (page *paymentIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "payment/index.gohtml", page) if httplib.IsHTMxRequest(r) && page.Filters.Paginated() {
template.MustRenderAdminNoLayout(w, r, user, company, "payment/results.gohtml", page)
} else {
template.MustRenderAdminFiles(w, r, user, company, page, "payment/index.gohtml", "payment/results.gohtml")
}
} }
type paymentDetails struct { type paymentDetails struct {

125
pkg/payment/filter.go Normal file
View File

@ -0,0 +1,125 @@
package payment
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/database"
"dev.tandem.ws/tandem/camper/pkg/form"
"dev.tandem.ws/tandem/camper/pkg/locale"
)
type filterForm struct {
company *auth.Company
PaymentStatus *form.Select
Reference *form.Input
FromDate *form.Input
ToDate *form.Input
Cursor *form.Cursor
}
func newFilterForm(ctx context.Context, conn *database.Conn, company *auth.Company, locale *locale.Locale) *filterForm {
return &filterForm{
company: company,
PaymentStatus: &form.Select{
Name: "payment_status",
Options: mustGetPaymentStatusOptions(ctx, conn, locale),
},
Reference: &form.Input{
Name: "reference",
},
FromDate: &form.Input{
Name: "from_date",
},
ToDate: &form.Input{
Name: "to_date",
},
Cursor: &form.Cursor{
Name: "cursor",
PerPage: 25,
},
}
}
func mustGetPaymentStatusOptions(ctx context.Context, conn *database.Conn, locale *locale.Locale) []*form.Option {
return form.MustGetOptions(ctx, conn, `
select payment_status.payment_status
, isi18n.name
from payment_status
join payment_status_i18n isi18n using(payment_status)
where isi18n.lang_tag = $1
order by payment_status`, locale.Language)
}
func (f *filterForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
f.PaymentStatus.FillValue(r)
f.Reference.FillValue(r)
f.FromDate.FillValue(r)
f.ToDate.FillValue(r)
f.Cursor.FillValue(r)
return nil
}
func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) {
var where []string
appendWhere := func(expression string, value interface{}) {
args = append(args, value)
where = append(where, fmt.Sprintf(expression, len(args)))
}
maybeAppendWhere := func(expression string, value string, conv func(string) interface{}) {
if value != "" {
if conv == nil {
appendWhere(expression, value)
} else {
appendWhere(expression, conv(value))
}
}
}
appendWhere("payment.company_id = $%d", f.company.ID)
maybeAppendWhere("payment.payment_status = $%d", f.PaymentStatus.String(), nil)
maybeAppendWhere("payment.reference like $%d", f.Reference.Val, func(v string) interface{} {
return "%" + v
})
maybeAppendWhere("payment.created_at >= $%d", f.FromDate.Val, nil)
maybeAppendWhere("payment.created_at <= $%d", f.ToDate.Val, nil)
if f.Paginated() {
params := f.Cursor.Params()
if len(params) == 2 {
where = append(where, fmt.Sprintf("(payment.created_at, payment_id) < ($%d, $%d)", len(args)+1, len(args)+2))
args = append(args, params[0])
args = append(args, params[1])
}
}
return strings.Join(where, ") AND ("), args
}
func (f *filterForm) buildCursor(customers []*paymentEntry) []*paymentEntry {
return form.BuildCursor(f.Cursor, customers, func(entry *paymentEntry) []string {
return []string{entry.CreatedAt.Format(database.ISODateTimeFormat), strconv.Itoa(entry.ID)}
})
}
func (f *filterForm) HasValue() bool {
return (len(f.PaymentStatus.Selected) > 0 && f.PaymentStatus.Selected[0] != "") ||
f.Reference.Val != "" ||
f.FromDate.Val != "" ||
f.ToDate.Val != ""
}
func (f *filterForm) PerPage() int {
return f.Cursor.PerPage
}
func (f *filterForm) Paginated() bool {
return f.Cursor.Pagination
}

View File

@ -24,6 +24,7 @@ import (
"dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/build" "dev.tandem.ws/tandem/camper/pkg/build"
"dev.tandem.ws/tandem/camper/pkg/database" "dev.tandem.ws/tandem/camper/pkg/database"
"dev.tandem.ws/tandem/camper/pkg/form"
httplib "dev.tandem.ws/tandem/camper/pkg/http" httplib "dev.tandem.ws/tandem/camper/pkg/http"
) )
@ -161,6 +162,10 @@ func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templ
return int(num) return int(num)
}, },
"slugify": Slugify, "slugify": Slugify,
"colspan": func(colspan int, cursor *form.Cursor) *form.Cursor {
cursor.Colspan = colspan
return cursor
},
}) })
templates = append(templates, "form.gohtml") templates = append(templates, "form.gohtml")
files := make([]string, len(templates)) files := make([]string, len(templates))

98
pkg/user/filter.go Normal file
View File

@ -0,0 +1,98 @@
package user
import (
"fmt"
"net/http"
"strconv"
"strings"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/database"
"dev.tandem.ws/tandem/camper/pkg/form"
)
type filterForm struct {
company *auth.Company
FromDate *form.Input
ToDate *form.Input
Cursor *form.Cursor
}
func newFilterForm() *filterForm {
return &filterForm{
FromDate: &form.Input{
Name: "from_date",
},
ToDate: &form.Input{
Name: "to_date",
},
Cursor: &form.Cursor{
Name: "cursor",
PerPage: 5,
},
}
}
func (f *filterForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
f.FromDate.FillValue(r)
f.ToDate.FillValue(r)
f.Cursor.FillValue(r)
return nil
}
func (f *filterForm) BuildQuery(args []interface{}) (string, []interface{}) {
var where []string
appendWhere := func(expression string, value interface{}) {
args = append(args, value)
where = append(where, fmt.Sprintf(expression, len(args)))
}
maybeAppendWhere := func(expression string, value string, conv func(string) interface{}) {
if value != "" {
if conv == nil {
appendWhere(expression, value)
} else {
appendWhere(expression, conv(value))
}
}
}
maybeAppendWhere("attempted_at >= $%d", f.FromDate.Val, nil)
maybeAppendWhere("attempted_at <= $%d", f.ToDate.Val, nil)
if f.Paginated() {
params := f.Cursor.Params()
if len(params) == 2 {
where = append(where, fmt.Sprintf("(attempted_at, attempt_id) < ($%d, $%d)", len(args)+1, len(args)+2))
args = append(args, params[0])
args = append(args, params[1])
}
}
if len(where) == 0 {
return "1=1", args
}
return strings.Join(where, ") AND ("), args
}
func (f *filterForm) buildCursor(customers []*loginAttemptEntry) []*loginAttemptEntry {
return form.BuildCursor(f.Cursor, customers, func(entry *loginAttemptEntry) []string {
return []string{entry.Date.Format(database.ISODateTimeFormat), strconv.Itoa(entry.ID)}
})
}
func (f *filterForm) HasValue() bool {
return f.FromDate.Val != "" ||
f.ToDate.Val != ""
}
func (f *filterForm) PerPage() int {
return f.Cursor.PerPage
}
func (f *filterForm) Paginated() bool {
return f.Cursor.Pagination
}

View File

@ -2,6 +2,8 @@ package user
import ( import (
"context" "context"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"fmt"
"net/http" "net/http"
"time" "time"
@ -11,32 +13,44 @@ import (
) )
func serveLoginAttemptIndex(w http.ResponseWriter, r *http.Request, loginAttempt *auth.User, company *auth.Company, conn *database.Conn) { func serveLoginAttemptIndex(w http.ResponseWriter, r *http.Request, loginAttempt *auth.User, company *auth.Company, conn *database.Conn) {
loginAttempts, err := collectLoginAttemptEntries(r.Context(), conn) filters := newFilterForm()
if err := filters.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
loginAttempts, err := collectLoginAttemptEntries(r.Context(), conn, filters)
if err != nil { if err != nil {
panic(err) panic(err)
} }
loginAttempts.MustRender(w, r, loginAttempt, company) index := &loginAttemptIndex{
Attempts: filters.buildCursor(loginAttempts),
Filters: filters,
}
index.MustRender(w, r, loginAttempt, company)
} }
func collectLoginAttemptEntries(ctx context.Context, conn *database.Conn) (loginAttemptIndex, error) { func collectLoginAttemptEntries(ctx context.Context, conn *database.Conn, filters *filterForm) ([]*loginAttemptEntry, error) {
rows, err := conn.Query(ctx, ` where, args := filters.BuildQuery(nil)
select user_name rows, err := conn.Query(ctx, fmt.Sprintf(`
select attempt_id
, user_name
, host(ip_address) , host(ip_address)
, attempted_at , attempted_at
, success , success
from company_login_attempt from company_login_attempt
order by attempted_at desc where (%s)
limit 500 order by attempted_at desc, attempt_id desc
`) limit %d
`, where, filters.PerPage()+1), args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var entries loginAttemptIndex var entries []*loginAttemptEntry
for rows.Next() { for rows.Next() {
entry := &loginAttemptEntry{} entry := &loginAttemptEntry{}
if err = rows.Scan(&entry.UserName, &entry.IPAddress, &entry.Date, &entry.Success); err != nil { if err = rows.Scan(&entry.ID, &entry.UserName, &entry.IPAddress, &entry.Date, &entry.Success); err != nil {
return nil, err return nil, err
} }
entries = append(entries, entry) entries = append(entries, entry)
@ -46,14 +60,22 @@ func collectLoginAttemptEntries(ctx context.Context, conn *database.Conn) (login
} }
type loginAttemptEntry struct { type loginAttemptEntry struct {
ID int
UserName string UserName string
IPAddress string IPAddress string
Date time.Time Date time.Time
Success bool Success bool
} }
type loginAttemptIndex []*loginAttemptEntry type loginAttemptIndex struct {
Attempts []*loginAttemptEntry
func (page *loginAttemptIndex) MustRender(w http.ResponseWriter, r *http.Request, loginAttempt *auth.User, company *auth.Company) { Filters *filterForm
template.MustRenderAdmin(w, r, loginAttempt, company, "user/login-attempts.gohtml", page) }
func (page *loginAttemptIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
if httplib.IsHTMxRequest(r) && page.Filters.Paginated() {
template.MustRenderAdminNoLayout(w, r, user, company, "user/results.gohtml", page)
} else {
template.MustRenderAdminFiles(w, r, user, company, page, "user/login-attempts.gohtml", "user/results.gohtml")
}
} }

275
po/ca.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: camper\n" "Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-05-03 17:19+0200\n" "POT-Creation-Date: 2024-05-13 10:37+0200\n"
"PO-Revision-Date: 2024-02-06 10:04+0100\n" "PO-Revision-Date: 2024-02-06 10:04+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n" "Language-Team: Catalan <ca@dodds.net>\n"
@ -78,7 +78,7 @@ msgid "Payment"
msgstr "Pagament" msgstr "Pagament"
#: web/templates/mail/payment/details.gotxt:9 #: web/templates/mail/payment/details.gotxt:9
#: web/templates/admin/payment/index.gohtml:21 #: web/templates/admin/payment/index.gohtml:73
#: web/templates/admin/payment/details.gohtml:52 #: web/templates/admin/payment/details.gohtml:52
#: web/templates/admin/prebooking/index.gohtml:59 #: web/templates/admin/prebooking/index.gohtml:59
#: web/templates/admin/booking/index.gohtml:73 #: web/templates/admin/booking/index.gohtml:73
@ -87,7 +87,7 @@ msgid "Reference"
msgstr "Referència" msgstr "Referència"
#: web/templates/mail/payment/details.gotxt:10 #: web/templates/mail/payment/details.gotxt:10
#: web/templates/admin/payment/index.gohtml:22 #: web/templates/admin/payment/index.gohtml:74
#: web/templates/admin/payment/details.gohtml:56 #: web/templates/admin/payment/details.gohtml:56
#: web/templates/admin/booking/index.gohtml:77 #: web/templates/admin/booking/index.gohtml:77
msgctxt "header" msgctxt "header"
@ -131,14 +131,14 @@ msgstr "Preferències dàrea"
#: web/templates/mail/payment/details.gotxt:18 #: web/templates/mail/payment/details.gotxt:18
#: web/templates/admin/payment/details.gohtml:82 #: web/templates/admin/payment/details.gohtml:82
msgctxt "input" msgctxt "input"
msgid "ACSI card?" msgid "ACSI / ANWB card?"
msgstr "Targeta ACSI?" msgstr "Targeta ACSI / ANWB?"
#: web/templates/mail/payment/details.gotxt:18 #: web/templates/mail/payment/details.gotxt:18
#: web/templates/admin/payment/details.gohtml:83 #: web/templates/admin/payment/details.gohtml:83
#: web/templates/admin/campsite/type/index.gohtml:53 #: web/templates/admin/campsite/type/index.gohtml:53
#: web/templates/admin/season/index.gohtml:44 #: web/templates/admin/season/index.gohtml:44
#: web/templates/admin/user/login-attempts.gohtml:31 #: web/templates/admin/user/results.gohtml:6
#: web/templates/admin/amenity/index.gohtml:40 #: web/templates/admin/amenity/index.gohtml:40
msgid "Yes" msgid "Yes"
msgstr "Sí" msgstr "Sí"
@ -147,7 +147,7 @@ msgstr "Sí"
#: web/templates/admin/payment/details.gohtml:83 #: web/templates/admin/payment/details.gohtml:83
#: web/templates/admin/campsite/type/index.gohtml:53 #: web/templates/admin/campsite/type/index.gohtml:53
#: web/templates/admin/season/index.gohtml:44 #: web/templates/admin/season/index.gohtml:44
#: web/templates/admin/user/login-attempts.gohtml:31 #: web/templates/admin/user/results.gohtml:6
#: web/templates/admin/amenity/index.gohtml:40 #: web/templates/admin/amenity/index.gohtml:40
msgid "No" msgid "No"
msgstr "No" msgstr "No"
@ -179,7 +179,7 @@ msgstr "Nits"
#: web/templates/mail/payment/details.gotxt:22 #: web/templates/mail/payment/details.gotxt:22
#: web/templates/public/booking/fields.gohtml:60 #: web/templates/public/booking/fields.gohtml:60
#: web/templates/admin/payment/details.gohtml:98 #: web/templates/admin/payment/details.gohtml:98
#: web/templates/admin/booking/fields.gohtml:93 pkg/invoice/admin.go:1062 #: web/templates/admin/booking/fields.gohtml:93 pkg/invoice/admin.go:964
msgctxt "input" msgctxt "input"
msgid "Adults aged 17 or older" msgid "Adults aged 17 or older"
msgstr "Adults de 17 anys o més" msgstr "Adults de 17 anys o més"
@ -187,7 +187,7 @@ msgstr "Adults de 17 anys o més"
#: web/templates/mail/payment/details.gotxt:23 #: web/templates/mail/payment/details.gotxt:23
#: web/templates/public/booking/fields.gohtml:71 #: web/templates/public/booking/fields.gohtml:71
#: web/templates/admin/payment/details.gohtml:102 #: web/templates/admin/payment/details.gohtml:102
#: web/templates/admin/booking/fields.gohtml:109 pkg/invoice/admin.go:1063 #: web/templates/admin/booking/fields.gohtml:109 pkg/invoice/admin.go:965
msgctxt "input" msgctxt "input"
msgid "Teenagers from 11 to 16 years old" msgid "Teenagers from 11 to 16 years old"
msgstr "Adolescents dentre 11 i 16 anys" msgstr "Adolescents dentre 11 i 16 anys"
@ -195,7 +195,7 @@ msgstr "Adolescents dentre 11 i 16 anys"
#: web/templates/mail/payment/details.gotxt:24 #: web/templates/mail/payment/details.gotxt:24
#: web/templates/public/booking/fields.gohtml:82 #: web/templates/public/booking/fields.gohtml:82
#: web/templates/admin/payment/details.gohtml:106 #: web/templates/admin/payment/details.gohtml:106
#: web/templates/admin/booking/fields.gohtml:125 pkg/invoice/admin.go:1064 #: web/templates/admin/booking/fields.gohtml:125 pkg/invoice/admin.go:966
msgctxt "input" msgctxt "input"
msgid "Children from 2 to 10 years old" msgid "Children from 2 to 10 years old"
msgstr "Nens dentre 2 i 10 anys" msgstr "Nens dentre 2 i 10 anys"
@ -203,14 +203,14 @@ msgstr "Nens dentre 2 i 10 anys"
#: web/templates/mail/payment/details.gotxt:25 #: web/templates/mail/payment/details.gotxt:25
#: web/templates/public/booking/fields.gohtml:100 #: web/templates/public/booking/fields.gohtml:100
#: web/templates/admin/payment/details.gohtml:110 #: web/templates/admin/payment/details.gohtml:110
#: web/templates/admin/booking/fields.gohtml:140 pkg/invoice/admin.go:1065 #: web/templates/admin/booking/fields.gohtml:140 pkg/invoice/admin.go:967
msgctxt "input" msgctxt "input"
msgid "Dogs" msgid "Dogs"
msgstr "Gossos" msgstr "Gossos"
#: web/templates/mail/payment/details.gotxt:26 #: web/templates/mail/payment/details.gotxt:26
#: web/templates/admin/payment/details.gohtml:114 #: web/templates/admin/payment/details.gohtml:114
#: web/templates/admin/booking/fields.gohtml:167 pkg/invoice/admin.go:1066 #: web/templates/admin/booking/fields.gohtml:167 pkg/invoice/admin.go:968
#: pkg/booking/cart.go:242 #: pkg/booking/cart.go:242
msgctxt "cart" msgctxt "cart"
msgid "Tourist tax" msgid "Tourist tax"
@ -293,6 +293,7 @@ msgstr "País"
#: web/templates/mail/payment/details.gotxt:46 #: web/templates/mail/payment/details.gotxt:46
#: web/templates/public/booking/fields.gohtml:201 #: web/templates/public/booking/fields.gohtml:201
#: web/templates/admin/payment/details.gohtml:163 #: web/templates/admin/payment/details.gohtml:163
#: web/templates/admin/customer/index.gohtml:33
#: web/templates/admin/login.gohtml:27 web/templates/admin/profile.gohtml:38 #: web/templates/admin/login.gohtml:27 web/templates/admin/profile.gohtml:38
#: web/templates/admin/taxDetails.gohtml:53 #: web/templates/admin/taxDetails.gohtml:53
msgctxt "input" msgctxt "input"
@ -418,7 +419,7 @@ msgid "Order Number"
msgstr "Número de comanda" msgstr "Número de comanda"
#: web/templates/public/payment/details.gohtml:8 #: web/templates/public/payment/details.gohtml:8
#: web/templates/admin/invoice/index.gohtml:103 #: web/templates/admin/invoice/index.gohtml:104
#: web/templates/admin/invoice/view.gohtml:26 #: web/templates/admin/invoice/view.gohtml:26
msgctxt "title" msgctxt "title"
msgid "Date" msgid "Date"
@ -596,6 +597,12 @@ msgctxt "action"
msgid "Filters" msgid "Filters"
msgstr "Filtres" msgstr "Filtres"
#: web/templates/public/form.gohtml:93 web/templates/admin/form.gohtml:93
#: web/templates/admin/prebooking/results.gohtml:20
msgctxt "action"
msgid "Load more"
msgstr "Carregan més"
#: web/templates/public/campsite/type.gohtml:49 #: web/templates/public/campsite/type.gohtml:49
#: web/templates/public/booking/fields.gohtml:278 #: web/templates/public/booking/fields.gohtml:278
msgctxt "action" msgctxt "action"
@ -1036,8 +1043,8 @@ msgstr "Esculli un país"
#: web/templates/public/booking/fields.gohtml:247 #: web/templates/public/booking/fields.gohtml:247
#: web/templates/admin/booking/fields.gohtml:259 #: web/templates/admin/booking/fields.gohtml:259
msgctxt "input" msgctxt "input"
msgid "ACSI card? (optional)" msgid "ACSI / ANWB card? (optional)"
msgstr "Targeta ACSI? (opcional)" msgstr "Targeta ACSI / ANWB? (opcional)"
#: web/templates/public/booking/fields.gohtml:255 #: web/templates/public/booking/fields.gohtml:255
msgctxt "input" msgctxt "input"
@ -1107,25 +1114,69 @@ msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Desa els canvis" msgstr "Desa els canvis"
#: web/templates/admin/payment/index.gohtml:20 #: web/templates/admin/payment/index.gohtml:24
#: web/templates/admin/user/login-attempts.gohtml:19 msgctxt "input"
msgid "Payment status"
msgstr "Estat del pagament"
#: web/templates/admin/payment/index.gohtml:28
#: web/templates/admin/invoice/index.gohtml:55
#: web/templates/admin/booking/index.gohtml:38
msgid "All statuses"
msgstr "Tots els estats"
#: web/templates/admin/payment/index.gohtml:36
#: web/templates/admin/invoice/index.gohtml:63
#: web/templates/admin/prebooking/index.gohtml:32
#: web/templates/admin/user/login-attempts.gohtml:24
#: web/templates/admin/booking/index.gohtml:46
msgctxt "input"
msgid "From date"
msgstr "De la data"
#: web/templates/admin/payment/index.gohtml:45
#: web/templates/admin/invoice/index.gohtml:72
#: web/templates/admin/prebooking/index.gohtml:41
#: web/templates/admin/user/login-attempts.gohtml:33
#: web/templates/admin/booking/index.gohtml:55
msgctxt "input"
msgid "To date"
msgstr "A la data"
#: web/templates/admin/payment/index.gohtml:54
msgctxt "input"
msgid "Reference"
msgstr "Referència"
#: web/templates/admin/payment/index.gohtml:64
#: web/templates/admin/customer/index.gohtml:43
#: web/templates/admin/invoice/index.gohtml:94
#: web/templates/admin/prebooking/index.gohtml:51
#: web/templates/admin/user/login-attempts.gohtml:43
#: web/templates/admin/booking/index.gohtml:65
msgctxt "action"
msgid "Reset"
msgstr "Restableix"
#: web/templates/admin/payment/index.gohtml:72
#: web/templates/admin/user/login-attempts.gohtml:51
msgctxt "header" msgctxt "header"
msgid "Date" msgid "Date"
msgstr "Data" msgstr "Data"
#: web/templates/admin/payment/index.gohtml:23 #: web/templates/admin/payment/index.gohtml:75
msgctxt "header" msgctxt "header"
msgid "Down payment" msgid "Down payment"
msgstr "A compte" msgstr "A compte"
#: web/templates/admin/payment/index.gohtml:24 #: web/templates/admin/payment/index.gohtml:76
#: web/templates/admin/booking/fields.gohtml:75 #: web/templates/admin/booking/fields.gohtml:75
#: web/templates/admin/booking/fields.gohtml:173 #: web/templates/admin/booking/fields.gohtml:173
msgctxt "header" msgctxt "header"
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
#: web/templates/admin/payment/index.gohtml:40 #: web/templates/admin/payment/index.gohtml:84
msgid "No payments found." msgid "No payments found."
msgstr "No sha trobat cap pagament." msgstr "No sha trobat cap pagament."
@ -1183,6 +1234,7 @@ msgstr "Àlies"
#: web/templates/admin/campsite/type/form.gohtml:51 #: web/templates/admin/campsite/type/form.gohtml:51
#: web/templates/admin/campsite/type/option/form.gohtml:41 #: web/templates/admin/campsite/type/option/form.gohtml:41
#: web/templates/admin/season/form.gohtml:50 #: web/templates/admin/season/form.gohtml:50
#: web/templates/admin/customer/index.gohtml:24
#: web/templates/admin/invoice/product-form.gohtml:16 #: web/templates/admin/invoice/product-form.gohtml:16
#: web/templates/admin/services/form.gohtml:53 #: web/templates/admin/services/form.gohtml:53
#: web/templates/admin/profile.gohtml:29 #: web/templates/admin/profile.gohtml:29
@ -1255,7 +1307,7 @@ msgstr "Afegeix text legal"
#: web/templates/admin/campsite/type/option/index.gohtml:30 #: web/templates/admin/campsite/type/option/index.gohtml:30
#: web/templates/admin/campsite/type/index.gohtml:29 #: web/templates/admin/campsite/type/index.gohtml:29
#: web/templates/admin/season/index.gohtml:29 #: web/templates/admin/season/index.gohtml:29
#: web/templates/admin/customer/index.gohtml:19 #: web/templates/admin/customer/index.gohtml:51
#: web/templates/admin/user/index.gohtml:20 #: web/templates/admin/user/index.gohtml:20
#: web/templates/admin/surroundings/index.gohtml:83 #: web/templates/admin/surroundings/index.gohtml:83
#: web/templates/admin/amenity/feature/index.gohtml:30 #: web/templates/admin/amenity/feature/index.gohtml:30
@ -1860,7 +1912,7 @@ msgid "New Customer"
msgstr "Nou client" msgstr "Nou client"
#: web/templates/admin/customer/form.gohtml:15 #: web/templates/admin/customer/form.gohtml:15
#: web/templates/admin/invoice/index.gohtml:105 #: web/templates/admin/invoice/index.gohtml:106
msgctxt "title" msgctxt "title"
msgid "Customer" msgid "Customer"
msgstr "Client" msgstr "Client"
@ -1917,19 +1969,19 @@ msgctxt "action"
msgid "Add Customer" msgid "Add Customer"
msgstr "Afegeix client" msgstr "Afegeix client"
#: web/templates/admin/customer/index.gohtml:20 #: web/templates/admin/customer/index.gohtml:52
#: web/templates/admin/user/login-attempts.gohtml:20 #: web/templates/admin/user/login-attempts.gohtml:52
#: web/templates/admin/user/index.gohtml:21 #: web/templates/admin/user/index.gohtml:21
msgctxt "header" msgctxt "header"
msgid "Email" msgid "Email"
msgstr "Correu-e" msgstr "Correu-e"
#: web/templates/admin/customer/index.gohtml:21 #: web/templates/admin/customer/index.gohtml:53
msgctxt "header" msgctxt "header"
msgid "Phone" msgid "Phone"
msgstr "Telèfon" msgstr "Telèfon"
#: web/templates/admin/customer/index.gohtml:33 #: web/templates/admin/customer/index.gohtml:61
msgid "No customer found." msgid "No customer found."
msgstr "No sha trobat cap client." msgstr "No sha trobat cap client."
@ -2047,25 +2099,6 @@ msgstr "Client"
msgid "All customers" msgid "All customers"
msgstr "Tots els clients" msgstr "Tots els clients"
#: web/templates/admin/invoice/index.gohtml:55
#: web/templates/admin/booking/index.gohtml:38
msgid "All statuses"
msgstr "Tots els estats"
#: web/templates/admin/invoice/index.gohtml:63
#: web/templates/admin/prebooking/index.gohtml:32
#: web/templates/admin/booking/index.gohtml:46
msgctxt "input"
msgid "From date"
msgstr "De la data"
#: web/templates/admin/invoice/index.gohtml:72
#: web/templates/admin/prebooking/index.gohtml:41
#: web/templates/admin/booking/index.gohtml:55
msgctxt "input"
msgid "To date"
msgstr "A la data"
#: web/templates/admin/invoice/index.gohtml:81 #: web/templates/admin/invoice/index.gohtml:81
msgctxt "input" msgctxt "input"
msgid "Invoice number" msgid "Invoice number"
@ -2076,60 +2109,39 @@ msgctxt "action"
msgid "Filter" msgid "Filter"
msgstr "Filtra" msgstr "Filtra"
#: web/templates/admin/invoice/index.gohtml:94
#: web/templates/admin/prebooking/index.gohtml:51
#: web/templates/admin/booking/index.gohtml:65
msgctxt "action"
msgid "Reset"
msgstr "Restableix"
#: web/templates/admin/invoice/index.gohtml:97 #: web/templates/admin/invoice/index.gohtml:97
msgctxt "action" msgctxt "action"
msgid "Add invoice" msgid "Add invoice"
msgstr "Afegeix factura" msgstr "Afegeix factura"
#: web/templates/admin/invoice/index.gohtml:102 #: web/templates/admin/invoice/index.gohtml:103
msgctxt "invoice" msgctxt "invoice"
msgid "All" msgid "All"
msgstr "Totes" msgstr "Totes"
#: web/templates/admin/invoice/index.gohtml:104 #: web/templates/admin/invoice/index.gohtml:105
msgctxt "title" msgctxt "title"
msgid "Invoice Num." msgid "Invoice Num."
msgstr "Núm. de factura" msgstr "Núm. de factura"
#: web/templates/admin/invoice/index.gohtml:106 #: web/templates/admin/invoice/index.gohtml:107
msgctxt "title" msgctxt "title"
msgid "Status" msgid "Status"
msgstr "Estat" msgstr "Estat"
#: web/templates/admin/invoice/index.gohtml:107 #: web/templates/admin/invoice/index.gohtml:108
msgctxt "title" msgctxt "title"
msgid "Download" msgid "Download"
msgstr "Descàrrega" msgstr "Descàrrega"
#: web/templates/admin/invoice/index.gohtml:108 #: web/templates/admin/invoice/index.gohtml:109
msgctxt "title" msgctxt "title"
msgid "Amount" msgid "Amount"
msgstr "Import" msgstr "Import"
#: web/templates/admin/invoice/index.gohtml:115 #: web/templates/admin/invoice/index.gohtml:117
msgctxt "action" msgid "No invoices found."
msgid "Select invoice %v" msgstr "No sha trobat cap factura."
msgstr "Selecciona la factura %v"
#: web/templates/admin/invoice/index.gohtml:144
msgctxt "action"
msgid "Download invoice %s"
msgstr "Descarrega la factura %s"
#: web/templates/admin/invoice/index.gohtml:154
msgid "No invoices added yet."
msgstr "No sha afegit cap factura encara."
#: web/templates/admin/invoice/index.gohtml:161
msgid "Total"
msgstr "Total"
#: web/templates/admin/invoice/view.gohtml:2 #: web/templates/admin/invoice/view.gohtml:2
msgctxt "title" msgctxt "title"
@ -2171,6 +2183,16 @@ msgctxt "title"
msgid "Tax Base" msgid "Tax Base"
msgstr "Base imposable" msgstr "Base imposable"
#: web/templates/admin/invoice/results.gohtml:3
msgctxt "action"
msgid "Select invoice %v"
msgstr "Selecciona la factura %v"
#: web/templates/admin/invoice/results.gohtml:32
msgctxt "action"
msgid "Download invoice %s"
msgstr "Descarrega la factura %s"
#: web/templates/admin/prebooking/index.gohtml:6 #: web/templates/admin/prebooking/index.gohtml:6
#: web/templates/admin/layout.gohtml:92 #: web/templates/admin/layout.gohtml:92
#: web/templates/admin/booking/form.gohtml:20 #: web/templates/admin/booking/form.gohtml:20
@ -2216,12 +2238,6 @@ msgstr "Nom del titular"
msgid "No prebooking found." msgid "No prebooking found."
msgstr "No sha trobat cap pre-reserva." msgstr "No sha trobat cap pre-reserva."
#: web/templates/admin/prebooking/results.gohtml:20
#: web/templates/admin/booking/results.gohtml:23
msgctxt "action"
msgid "Load more"
msgstr "Carregan més"
#: web/templates/admin/login.gohtml:6 web/templates/admin/login.gohtml:18 #: web/templates/admin/login.gohtml:6 web/templates/admin/login.gohtml:18
msgctxt "title" msgctxt "title"
msgid "Login" msgid "Login"
@ -2307,7 +2323,7 @@ msgid "Password Confirmation"
msgstr "Confirmació de la contrasenya" msgstr "Confirmació de la contrasenya"
#: web/templates/admin/user/login-attempts.gohtml:6 #: web/templates/admin/user/login-attempts.gohtml:6
#: web/templates/admin/user/login-attempts.gohtml:15 #: web/templates/admin/user/login-attempts.gohtml:46
msgctxt "title" msgctxt "title"
msgid "Login Attempts" msgid "Login Attempts"
msgstr "Intents dentrada" msgstr "Intents dentrada"
@ -2320,16 +2336,20 @@ msgctxt "title"
msgid "Users" msgid "Users"
msgstr "Usuaris" msgstr "Usuaris"
#: web/templates/admin/user/login-attempts.gohtml:21 #: web/templates/admin/user/login-attempts.gohtml:53
msgctxt "header" msgctxt "header"
msgid "IP Address" msgid "IP Address"
msgstr "Adreça IP" msgstr "Adreça IP"
#: web/templates/admin/user/login-attempts.gohtml:22 #: web/templates/admin/user/login-attempts.gohtml:54
msgctxt "header" msgctxt "header"
msgid "Success" msgid "Success"
msgstr "Èxit" msgstr "Èxit"
#: web/templates/admin/user/login-attempts.gohtml:62
msgid "No logging attempts found."
msgstr "No sha trobat cap intent dentrada."
#: web/templates/admin/user/index.gohtml:14 #: web/templates/admin/user/index.gohtml:14
msgctxt "action" msgctxt "action"
msgid "Add User" msgid "Add User"
@ -2701,7 +2721,7 @@ msgctxt "header"
msgid "Decription" msgid "Decription"
msgstr "Descripció" msgstr "Descripció"
#: web/templates/admin/booking/fields.gohtml:81 pkg/invoice/admin.go:1061 #: web/templates/admin/booking/fields.gohtml:81 pkg/invoice/admin.go:963
#: pkg/booking/cart.go:232 #: pkg/booking/cart.go:232
msgctxt "cart" msgctxt "cart"
msgid "Night" msgid "Night"
@ -2900,7 +2920,7 @@ msgstr "Rebut amb èxit el pagament de la reserva"
#: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365 #: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365
#: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577 #: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577
#: pkg/campsite/feature.go:269 pkg/season/admin.go:411 #: pkg/campsite/feature.go:269 pkg/season/admin.go:411
#: pkg/invoice/admin.go:1158 pkg/services/admin.go:316 #: pkg/invoice/admin.go:1060 pkg/services/admin.go:316
#: pkg/surroundings/admin.go:340 pkg/amenity/feature.go:269 #: pkg/surroundings/admin.go:340 pkg/amenity/feature.go:269
#: pkg/amenity/admin.go:283 #: pkg/amenity/admin.go:283
msgid "Name can not be empty." msgid "Name can not be empty."
@ -2941,8 +2961,8 @@ msgstr "La imatge de la diapositiva ha de ser un mèdia de tipus imatge."
msgid "Email can not be empty." msgid "Email can not be empty."
msgstr "No podeu deixar el correu-e en blanc." msgstr "No podeu deixar el correu-e en blanc."
#: pkg/app/login.go:57 pkg/app/user.go:247 pkg/customer/admin.go:345 #: pkg/app/login.go:57 pkg/app/user.go:247 pkg/customer/admin.go:361
#: pkg/company/admin.go:225 pkg/booking/admin.go:597 pkg/booking/public.go:593 #: pkg/company/admin.go:225 pkg/booking/admin.go:479 pkg/booking/public.go:593
msgid "This email is not valid. It should be like name@domain.com." msgid "This email is not valid. It should be like name@domain.com."
msgstr "Aquest correu-e no és vàlid. Hauria de ser similar a nom@domini.com." msgstr "Aquest correu-e no és vàlid. Hauria de ser similar a nom@domini.com."
@ -2999,15 +3019,15 @@ msgstr "El valor del màxim ha de ser un número enter."
msgid "Maximum must be equal or greater than minimum." msgid "Maximum must be equal or greater than minimum."
msgstr "El valor del màxim ha de ser igual o superir al del mínim." msgstr "El valor del màxim ha de ser igual o superir al del mínim."
#: pkg/campsite/types/option.go:382 pkg/invoice/admin.go:1159 #: pkg/campsite/types/option.go:382 pkg/invoice/admin.go:1061
msgid "Price can not be empty." msgid "Price can not be empty."
msgstr "No podeu deixar el preu en blanc." msgstr "No podeu deixar el preu en blanc."
#: pkg/campsite/types/option.go:383 pkg/invoice/admin.go:1160 #: pkg/campsite/types/option.go:383 pkg/invoice/admin.go:1062
msgid "Price must be a decimal number." msgid "Price must be a decimal number."
msgstr "El preu ha de ser un número decimal." msgstr "El preu ha de ser un número decimal."
#: pkg/campsite/types/option.go:384 pkg/invoice/admin.go:1161 #: pkg/campsite/types/option.go:384 pkg/invoice/admin.go:1063
msgid "Price must be zero or greater." msgid "Price must be zero or greater."
msgstr "El preu ha de ser com a mínim zero." msgstr "El preu ha de ser com a mínim zero."
@ -3153,7 +3173,7 @@ msgctxt "header"
msgid "Children (aged 2 to 10)" msgid "Children (aged 2 to 10)"
msgstr "Mainada (entre 2 i 10 anys)" msgstr "Mainada (entre 2 i 10 anys)"
#: pkg/campsite/admin.go:280 pkg/booking/admin.go:573 pkg/booking/public.go:177 #: pkg/campsite/admin.go:280 pkg/booking/admin.go:455 pkg/booking/public.go:177
#: pkg/booking/public.go:232 #: pkg/booking/public.go:232
msgid "Selected campsite type is not valid." msgid "Selected campsite type is not valid."
msgstr "El tipus dallotjament escollit no és vàlid." msgstr "El tipus dallotjament escollit no és vàlid."
@ -3191,129 +3211,129 @@ msgstr "No podeu deixar la data de fi en blanc."
msgid "End date must be a valid date." msgid "End date must be a valid date."
msgstr "La data de fi ha de ser una data vàlida." msgstr "La data de fi ha de ser una data vàlida."
#: pkg/customer/admin.go:326 pkg/company/admin.go:207 #: pkg/customer/admin.go:342 pkg/company/admin.go:207
#: pkg/booking/checkin.go:300 pkg/booking/public.go:577 #: pkg/booking/checkin.go:300 pkg/booking/public.go:577
msgid "Selected country is not valid." msgid "Selected country is not valid."
msgstr "El país escollit no és vàlid." msgstr "El país escollit no és vàlid."
#: pkg/customer/admin.go:330 pkg/booking/checkin.go:284 #: pkg/customer/admin.go:346 pkg/booking/checkin.go:284
msgid "Selected ID document type is not valid." msgid "Selected ID document type is not valid."
msgstr "El tipus de document didentitat escollit no és vàlid." msgstr "El tipus de document didentitat escollit no és vàlid."
#: pkg/customer/admin.go:331 pkg/booking/checkin.go:285 #: pkg/customer/admin.go:347 pkg/booking/checkin.go:285
msgid "ID document number can not be empty." msgid "ID document number can not be empty."
msgstr "No podeu deixar el número document didentitat en blanc." msgstr "No podeu deixar el número document didentitat en blanc."
#: pkg/customer/admin.go:333 pkg/booking/checkin.go:291 #: pkg/customer/admin.go:349 pkg/booking/checkin.go:291
#: pkg/booking/checkin.go:292 pkg/booking/admin.go:585 #: pkg/booking/checkin.go:292 pkg/booking/admin.go:467
#: pkg/booking/public.go:581 #: pkg/booking/public.go:581
msgid "Full name can not be empty." msgid "Full name can not be empty."
msgstr "No podeu deixar el nom i els cognoms en blanc." msgstr "No podeu deixar el nom i els cognoms en blanc."
#: pkg/customer/admin.go:334 pkg/booking/admin.go:586 pkg/booking/public.go:582 #: pkg/customer/admin.go:350 pkg/booking/admin.go:468 pkg/booking/public.go:582
msgid "Full name must have at least one letter." msgid "Full name must have at least one letter."
msgstr "El nom i els cognoms han de tenir com a mínim una lletra." msgstr "El nom i els cognoms han de tenir com a mínim una lletra."
#: pkg/customer/admin.go:337 pkg/company/admin.go:230 pkg/booking/public.go:585 #: pkg/customer/admin.go:353 pkg/company/admin.go:230 pkg/booking/public.go:585
msgid "Address can not be empty." msgid "Address can not be empty."
msgstr "No podeu deixar ladreça en blanc." msgstr "No podeu deixar ladreça en blanc."
#: pkg/customer/admin.go:338 pkg/booking/public.go:586 #: pkg/customer/admin.go:354 pkg/booking/public.go:586
msgid "Town or village can not be empty." msgid "Town or village can not be empty."
msgstr "No podeu deixar la població en blanc." msgstr "No podeu deixar la població en blanc."
#: pkg/customer/admin.go:339 pkg/company/admin.go:233 pkg/booking/public.go:587 #: pkg/customer/admin.go:355 pkg/company/admin.go:233 pkg/booking/public.go:587
msgid "Postcode can not be empty." msgid "Postcode can not be empty."
msgstr "No podeu deixar el codi postal en blanc." msgstr "No podeu deixar el codi postal en blanc."
#: pkg/customer/admin.go:340 pkg/company/admin.go:234 pkg/booking/admin.go:592 #: pkg/customer/admin.go:356 pkg/company/admin.go:234 pkg/booking/admin.go:474
#: pkg/booking/public.go:588 #: pkg/booking/public.go:588
msgid "This postcode is not valid." msgid "This postcode is not valid."
msgstr "Aquest codi postal no és vàlid." msgstr "Aquest codi postal no és vàlid."
#: pkg/customer/admin.go:348 pkg/company/admin.go:220 #: pkg/customer/admin.go:364 pkg/company/admin.go:220
#: pkg/booking/checkin.go:304 pkg/booking/admin.go:602 #: pkg/booking/checkin.go:304 pkg/booking/admin.go:484
#: pkg/booking/public.go:596 #: pkg/booking/public.go:596
msgid "This phone number is not valid." msgid "This phone number is not valid."
msgstr "Aquest número de telèfon no és vàlid." msgstr "Aquest número de telèfon no és vàlid."
#: pkg/invoice/admin.go:679 #: pkg/invoice/admin.go:581
msgctxt "filename" msgctxt "filename"
msgid "invoices.zip" msgid "invoices.zip"
msgstr "factures.zip" msgstr "factures.zip"
#: pkg/invoice/admin.go:694 #: pkg/invoice/admin.go:596
msgctxt "filename" msgctxt "filename"
msgid "invoices.ods" msgid "invoices.ods"
msgstr "factures.ods" msgstr "factures.ods"
#: pkg/invoice/admin.go:696 pkg/invoice/admin.go:1358 pkg/invoice/admin.go:1365 #: pkg/invoice/admin.go:598 pkg/invoice/admin.go:1260 pkg/invoice/admin.go:1267
msgid "Invalid action" msgid "Invalid action"
msgstr "Acció invàlida" msgstr "Acció invàlida"
#: pkg/invoice/admin.go:861 #: pkg/invoice/admin.go:763
msgid "Selected invoice status is not valid." msgid "Selected invoice status is not valid."
msgstr "Lestat de factura escollit no és vàlid." msgstr "Lestat de factura escollit no és vàlid."
#: pkg/invoice/admin.go:862 #: pkg/invoice/admin.go:764
msgid "Invoice date can not be empty." msgid "Invoice date can not be empty."
msgstr "No podeu deixar la data de factura en blanc." msgstr "No podeu deixar la data de factura en blanc."
#: pkg/invoice/admin.go:863 #: pkg/invoice/admin.go:765
msgid "Invoice date must be a valid date." msgid "Invoice date must be a valid date."
msgstr "La data de factura ha de ser una data vàlida." msgstr "La data de factura ha de ser una data vàlida."
#: pkg/invoice/admin.go:1021 #: pkg/invoice/admin.go:923
#, c-format #, c-format
msgid "Re: booking #%s of %s%s" msgid "Re: booking #%s of %s%s"
msgstr "Ref: reserva núm. %s del %s-%s" msgstr "Ref: reserva núm. %s del %s-%s"
#: pkg/invoice/admin.go:1022 #: pkg/invoice/admin.go:924
msgctxt "to_char" msgctxt "to_char"
msgid "MM/DD/YYYY" msgid "MM/DD/YYYY"
msgstr "DD/MM/YYYY" msgstr "DD/MM/YYYY"
#: pkg/invoice/admin.go:1149 #: pkg/invoice/admin.go:1051
msgid "Invoice product ID must be an integer." msgid "Invoice product ID must be an integer."
msgstr "LID de producte de factura ha de ser enter." msgstr "LID de producte de factura ha de ser enter."
#: pkg/invoice/admin.go:1150 #: pkg/invoice/admin.go:1052
msgid "Invoice product ID one or greater." msgid "Invoice product ID one or greater."
msgstr "LID de producte de factura ha de ser com a mínim u." msgstr "LID de producte de factura ha de ser com a mínim u."
#: pkg/invoice/admin.go:1154 #: pkg/invoice/admin.go:1056
msgid "Product ID must be an integer." msgid "Product ID must be an integer."
msgstr "LID de producte ha de ser un número enter." msgstr "LID de producte ha de ser un número enter."
#: pkg/invoice/admin.go:1155 #: pkg/invoice/admin.go:1057
msgid "Product ID must zero or greater." msgid "Product ID must zero or greater."
msgstr "LID de producte ha de ser com a mínim zero." msgstr "LID de producte ha de ser com a mínim zero."
#: pkg/invoice/admin.go:1164 #: pkg/invoice/admin.go:1066
msgid "Quantity can not be empty." msgid "Quantity can not be empty."
msgstr "No podeu deixar la quantitat en blanc." msgstr "No podeu deixar la quantitat en blanc."
#: pkg/invoice/admin.go:1165 #: pkg/invoice/admin.go:1067
msgid "Quantity must be an integer." msgid "Quantity must be an integer."
msgstr "La quantitat ha de ser un número enter." msgstr "La quantitat ha de ser un número enter."
#: pkg/invoice/admin.go:1166 #: pkg/invoice/admin.go:1068
msgid "Quantity must one or greater." msgid "Quantity must one or greater."
msgstr "La quantitat ha de ser com a mínim u." msgstr "La quantitat ha de ser com a mínim u."
#: pkg/invoice/admin.go:1169 #: pkg/invoice/admin.go:1071
msgid "Discount can not be empty." msgid "Discount can not be empty."
msgstr "No podeu deixar el descompte en blanc." msgstr "No podeu deixar el descompte en blanc."
#: pkg/invoice/admin.go:1170 #: pkg/invoice/admin.go:1072
msgid "Discount must be an integer." msgid "Discount must be an integer."
msgstr "El descompte ha de ser un número enter." msgstr "El descompte ha de ser un número enter."
#: pkg/invoice/admin.go:1171 pkg/invoice/admin.go:1172 #: pkg/invoice/admin.go:1073 pkg/invoice/admin.go:1074
msgid "Discount must be a percentage between 0 and 100." msgid "Discount must be a percentage between 0 and 100."
msgstr "El descompte ha de ser un percentatge entre 0 i 100" msgstr "El descompte ha de ser un percentatge entre 0 i 100"
#: pkg/invoice/admin.go:1176 #: pkg/invoice/admin.go:1078
msgid "Selected tax is not valid." msgid "Selected tax is not valid."
msgstr "Limpost escollit no és vàlid." msgstr "Limpost escollit no és vàlid."
@ -3501,19 +3521,19 @@ msgctxt "filename"
msgid "bookings.ods" msgid "bookings.ods"
msgstr "reserves.ods" msgstr "reserves.ods"
#: pkg/booking/admin.go:591 #: pkg/booking/admin.go:473
msgid "Country can not be empty to validate the postcode." msgid "Country can not be empty to validate the postcode."
msgstr "No podeu deixar el país en blanc per validar el codi postal." msgstr "No podeu deixar el país en blanc per validar el codi postal."
#: pkg/booking/admin.go:601 #: pkg/booking/admin.go:483
msgid "Country can not be empty to validate the phone." msgid "Country can not be empty to validate the phone."
msgstr "No podeu deixar el país en blanc per validar el telèfon." msgstr "No podeu deixar el país en blanc per validar el telèfon."
#: pkg/booking/admin.go:608 #: pkg/booking/admin.go:490
msgid "You must select at least one accommodation." msgid "You must select at least one accommodation."
msgstr "Heu descollir com a mínim un allotjament." msgstr "Heu descollir com a mínim un allotjament."
#: pkg/booking/admin.go:614 #: pkg/booking/admin.go:496
msgid "The selected accommodations have no available openings in the requested dates." msgid "The selected accommodations have no available openings in the requested dates."
msgstr "Els allotjaments escollits no estan disponibles a les dates demanades." msgstr "Els allotjaments escollits no estan disponibles a les dates demanades."
@ -3630,6 +3650,9 @@ msgstr "El valor de %s ha de ser com a màxim %d."
msgid "It is mandatory to agree to the reservation conditions." msgid "It is mandatory to agree to the reservation conditions."
msgstr "És obligatori acceptar les condicions de reserves." msgstr "És obligatori acceptar les condicions de reserves."
#~ msgid "Total"
#~ msgstr "Total"
#~ msgid "Select a customer" #~ msgid "Select a customer"
#~ msgstr "Esculliu un client" #~ msgstr "Esculliu un client"

275
po/es.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: camper\n" "Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-05-03 17:19+0200\n" "POT-Creation-Date: 2024-05-13 10:37+0200\n"
"PO-Revision-Date: 2024-02-06 10:04+0100\n" "PO-Revision-Date: 2024-02-06 10:04+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n" "Language-Team: Spanish <es@tp.org.es>\n"
@ -78,7 +78,7 @@ msgid "Payment"
msgstr "Pago" msgstr "Pago"
#: web/templates/mail/payment/details.gotxt:9 #: web/templates/mail/payment/details.gotxt:9
#: web/templates/admin/payment/index.gohtml:21 #: web/templates/admin/payment/index.gohtml:73
#: web/templates/admin/payment/details.gohtml:52 #: web/templates/admin/payment/details.gohtml:52
#: web/templates/admin/prebooking/index.gohtml:59 #: web/templates/admin/prebooking/index.gohtml:59
#: web/templates/admin/booking/index.gohtml:73 #: web/templates/admin/booking/index.gohtml:73
@ -87,7 +87,7 @@ msgid "Reference"
msgstr "Referencia" msgstr "Referencia"
#: web/templates/mail/payment/details.gotxt:10 #: web/templates/mail/payment/details.gotxt:10
#: web/templates/admin/payment/index.gohtml:22 #: web/templates/admin/payment/index.gohtml:74
#: web/templates/admin/payment/details.gohtml:56 #: web/templates/admin/payment/details.gohtml:56
#: web/templates/admin/booking/index.gohtml:77 #: web/templates/admin/booking/index.gohtml:77
msgctxt "header" msgctxt "header"
@ -131,14 +131,14 @@ msgstr "Preferencias de área"
#: web/templates/mail/payment/details.gotxt:18 #: web/templates/mail/payment/details.gotxt:18
#: web/templates/admin/payment/details.gohtml:82 #: web/templates/admin/payment/details.gohtml:82
msgctxt "input" msgctxt "input"
msgid "ACSI card?" msgid "ACSI / ANWB card?"
msgstr "¿Tarjeta ACSI?" msgstr "¿Tarjeta ACSI / ANWB?"
#: web/templates/mail/payment/details.gotxt:18 #: web/templates/mail/payment/details.gotxt:18
#: web/templates/admin/payment/details.gohtml:83 #: web/templates/admin/payment/details.gohtml:83
#: web/templates/admin/campsite/type/index.gohtml:53 #: web/templates/admin/campsite/type/index.gohtml:53
#: web/templates/admin/season/index.gohtml:44 #: web/templates/admin/season/index.gohtml:44
#: web/templates/admin/user/login-attempts.gohtml:31 #: web/templates/admin/user/results.gohtml:6
#: web/templates/admin/amenity/index.gohtml:40 #: web/templates/admin/amenity/index.gohtml:40
msgid "Yes" msgid "Yes"
msgstr "Sí" msgstr "Sí"
@ -147,7 +147,7 @@ msgstr "Sí"
#: web/templates/admin/payment/details.gohtml:83 #: web/templates/admin/payment/details.gohtml:83
#: web/templates/admin/campsite/type/index.gohtml:53 #: web/templates/admin/campsite/type/index.gohtml:53
#: web/templates/admin/season/index.gohtml:44 #: web/templates/admin/season/index.gohtml:44
#: web/templates/admin/user/login-attempts.gohtml:31 #: web/templates/admin/user/results.gohtml:6
#: web/templates/admin/amenity/index.gohtml:40 #: web/templates/admin/amenity/index.gohtml:40
msgid "No" msgid "No"
msgstr "No" msgstr "No"
@ -179,7 +179,7 @@ msgstr "Noches"
#: web/templates/mail/payment/details.gotxt:22 #: web/templates/mail/payment/details.gotxt:22
#: web/templates/public/booking/fields.gohtml:60 #: web/templates/public/booking/fields.gohtml:60
#: web/templates/admin/payment/details.gohtml:98 #: web/templates/admin/payment/details.gohtml:98
#: web/templates/admin/booking/fields.gohtml:93 pkg/invoice/admin.go:1062 #: web/templates/admin/booking/fields.gohtml:93 pkg/invoice/admin.go:964
msgctxt "input" msgctxt "input"
msgid "Adults aged 17 or older" msgid "Adults aged 17 or older"
msgstr "Adultos de 17 años o más" msgstr "Adultos de 17 años o más"
@ -187,7 +187,7 @@ msgstr "Adultos de 17 años o más"
#: web/templates/mail/payment/details.gotxt:23 #: web/templates/mail/payment/details.gotxt:23
#: web/templates/public/booking/fields.gohtml:71 #: web/templates/public/booking/fields.gohtml:71
#: web/templates/admin/payment/details.gohtml:102 #: web/templates/admin/payment/details.gohtml:102
#: web/templates/admin/booking/fields.gohtml:109 pkg/invoice/admin.go:1063 #: web/templates/admin/booking/fields.gohtml:109 pkg/invoice/admin.go:965
msgctxt "input" msgctxt "input"
msgid "Teenagers from 11 to 16 years old" msgid "Teenagers from 11 to 16 years old"
msgstr "Adolescentes de 11 a 16 años" msgstr "Adolescentes de 11 a 16 años"
@ -195,7 +195,7 @@ msgstr "Adolescentes de 11 a 16 años"
#: web/templates/mail/payment/details.gotxt:24 #: web/templates/mail/payment/details.gotxt:24
#: web/templates/public/booking/fields.gohtml:82 #: web/templates/public/booking/fields.gohtml:82
#: web/templates/admin/payment/details.gohtml:106 #: web/templates/admin/payment/details.gohtml:106
#: web/templates/admin/booking/fields.gohtml:125 pkg/invoice/admin.go:1064 #: web/templates/admin/booking/fields.gohtml:125 pkg/invoice/admin.go:966
msgctxt "input" msgctxt "input"
msgid "Children from 2 to 10 years old" msgid "Children from 2 to 10 years old"
msgstr "Niños de 2 a 10 años" msgstr "Niños de 2 a 10 años"
@ -203,14 +203,14 @@ msgstr "Niños de 2 a 10 años"
#: web/templates/mail/payment/details.gotxt:25 #: web/templates/mail/payment/details.gotxt:25
#: web/templates/public/booking/fields.gohtml:100 #: web/templates/public/booking/fields.gohtml:100
#: web/templates/admin/payment/details.gohtml:110 #: web/templates/admin/payment/details.gohtml:110
#: web/templates/admin/booking/fields.gohtml:140 pkg/invoice/admin.go:1065 #: web/templates/admin/booking/fields.gohtml:140 pkg/invoice/admin.go:967
msgctxt "input" msgctxt "input"
msgid "Dogs" msgid "Dogs"
msgstr "Perros" msgstr "Perros"
#: web/templates/mail/payment/details.gotxt:26 #: web/templates/mail/payment/details.gotxt:26
#: web/templates/admin/payment/details.gohtml:114 #: web/templates/admin/payment/details.gohtml:114
#: web/templates/admin/booking/fields.gohtml:167 pkg/invoice/admin.go:1066 #: web/templates/admin/booking/fields.gohtml:167 pkg/invoice/admin.go:968
#: pkg/booking/cart.go:242 #: pkg/booking/cart.go:242
msgctxt "cart" msgctxt "cart"
msgid "Tourist tax" msgid "Tourist tax"
@ -293,6 +293,7 @@ msgstr "País"
#: web/templates/mail/payment/details.gotxt:46 #: web/templates/mail/payment/details.gotxt:46
#: web/templates/public/booking/fields.gohtml:201 #: web/templates/public/booking/fields.gohtml:201
#: web/templates/admin/payment/details.gohtml:163 #: web/templates/admin/payment/details.gohtml:163
#: web/templates/admin/customer/index.gohtml:33
#: web/templates/admin/login.gohtml:27 web/templates/admin/profile.gohtml:38 #: web/templates/admin/login.gohtml:27 web/templates/admin/profile.gohtml:38
#: web/templates/admin/taxDetails.gohtml:53 #: web/templates/admin/taxDetails.gohtml:53
msgctxt "input" msgctxt "input"
@ -418,7 +419,7 @@ msgid "Order Number"
msgstr "Número de pedido" msgstr "Número de pedido"
#: web/templates/public/payment/details.gohtml:8 #: web/templates/public/payment/details.gohtml:8
#: web/templates/admin/invoice/index.gohtml:103 #: web/templates/admin/invoice/index.gohtml:104
#: web/templates/admin/invoice/view.gohtml:26 #: web/templates/admin/invoice/view.gohtml:26
msgctxt "title" msgctxt "title"
msgid "Date" msgid "Date"
@ -596,6 +597,12 @@ msgctxt "action"
msgid "Filters" msgid "Filters"
msgstr "Filtros" msgstr "Filtros"
#: web/templates/public/form.gohtml:93 web/templates/admin/form.gohtml:93
#: web/templates/admin/prebooking/results.gohtml:20
msgctxt "action"
msgid "Load more"
msgstr "Cargar más"
#: web/templates/public/campsite/type.gohtml:49 #: web/templates/public/campsite/type.gohtml:49
#: web/templates/public/booking/fields.gohtml:278 #: web/templates/public/booking/fields.gohtml:278
msgctxt "action" msgctxt "action"
@ -1036,8 +1043,8 @@ msgstr "Escoja un país"
#: web/templates/public/booking/fields.gohtml:247 #: web/templates/public/booking/fields.gohtml:247
#: web/templates/admin/booking/fields.gohtml:259 #: web/templates/admin/booking/fields.gohtml:259
msgctxt "input" msgctxt "input"
msgid "ACSI card? (optional)" msgid "ACSI / ANWB card? (optional)"
msgstr "¿Tarjeta ACSI? (opcional)" msgstr "¿Tarjeta ACSI / ANWB? (opcional)"
#: web/templates/public/booking/fields.gohtml:255 #: web/templates/public/booking/fields.gohtml:255
msgctxt "input" msgctxt "input"
@ -1107,25 +1114,69 @@ msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Guardar los cambios" msgstr "Guardar los cambios"
#: web/templates/admin/payment/index.gohtml:20 #: web/templates/admin/payment/index.gohtml:24
#: web/templates/admin/user/login-attempts.gohtml:19 msgctxt "input"
msgid "Payment status"
msgstr "Estado del pago"
#: web/templates/admin/payment/index.gohtml:28
#: web/templates/admin/invoice/index.gohtml:55
#: web/templates/admin/booking/index.gohtml:38
msgid "All statuses"
msgstr "Todos los estados"
#: web/templates/admin/payment/index.gohtml:36
#: web/templates/admin/invoice/index.gohtml:63
#: web/templates/admin/prebooking/index.gohtml:32
#: web/templates/admin/user/login-attempts.gohtml:24
#: web/templates/admin/booking/index.gohtml:46
msgctxt "input"
msgid "From date"
msgstr "De la fecha"
#: web/templates/admin/payment/index.gohtml:45
#: web/templates/admin/invoice/index.gohtml:72
#: web/templates/admin/prebooking/index.gohtml:41
#: web/templates/admin/user/login-attempts.gohtml:33
#: web/templates/admin/booking/index.gohtml:55
msgctxt "input"
msgid "To date"
msgstr "A la fecha"
#: web/templates/admin/payment/index.gohtml:54
msgctxt "input"
msgid "Reference"
msgstr "Referencia"
#: web/templates/admin/payment/index.gohtml:64
#: web/templates/admin/customer/index.gohtml:43
#: web/templates/admin/invoice/index.gohtml:94
#: web/templates/admin/prebooking/index.gohtml:51
#: web/templates/admin/user/login-attempts.gohtml:43
#: web/templates/admin/booking/index.gohtml:65
msgctxt "action"
msgid "Reset"
msgstr "Restablecer"
#: web/templates/admin/payment/index.gohtml:72
#: web/templates/admin/user/login-attempts.gohtml:51
msgctxt "header" msgctxt "header"
msgid "Date" msgid "Date"
msgstr "Fecha" msgstr "Fecha"
#: web/templates/admin/payment/index.gohtml:23 #: web/templates/admin/payment/index.gohtml:75
msgctxt "header" msgctxt "header"
msgid "Down payment" msgid "Down payment"
msgstr "A cuenta" msgstr "A cuenta"
#: web/templates/admin/payment/index.gohtml:24 #: web/templates/admin/payment/index.gohtml:76
#: web/templates/admin/booking/fields.gohtml:75 #: web/templates/admin/booking/fields.gohtml:75
#: web/templates/admin/booking/fields.gohtml:173 #: web/templates/admin/booking/fields.gohtml:173
msgctxt "header" msgctxt "header"
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
#: web/templates/admin/payment/index.gohtml:40 #: web/templates/admin/payment/index.gohtml:84
msgid "No payments found." msgid "No payments found."
msgstr "No se ha encontrado ningún pago." msgstr "No se ha encontrado ningún pago."
@ -1183,6 +1234,7 @@ msgstr "Álias"
#: web/templates/admin/campsite/type/form.gohtml:51 #: web/templates/admin/campsite/type/form.gohtml:51
#: web/templates/admin/campsite/type/option/form.gohtml:41 #: web/templates/admin/campsite/type/option/form.gohtml:41
#: web/templates/admin/season/form.gohtml:50 #: web/templates/admin/season/form.gohtml:50
#: web/templates/admin/customer/index.gohtml:24
#: web/templates/admin/invoice/product-form.gohtml:16 #: web/templates/admin/invoice/product-form.gohtml:16
#: web/templates/admin/services/form.gohtml:53 #: web/templates/admin/services/form.gohtml:53
#: web/templates/admin/profile.gohtml:29 #: web/templates/admin/profile.gohtml:29
@ -1255,7 +1307,7 @@ msgstr "Añadir texto legal"
#: web/templates/admin/campsite/type/option/index.gohtml:30 #: web/templates/admin/campsite/type/option/index.gohtml:30
#: web/templates/admin/campsite/type/index.gohtml:29 #: web/templates/admin/campsite/type/index.gohtml:29
#: web/templates/admin/season/index.gohtml:29 #: web/templates/admin/season/index.gohtml:29
#: web/templates/admin/customer/index.gohtml:19 #: web/templates/admin/customer/index.gohtml:51
#: web/templates/admin/user/index.gohtml:20 #: web/templates/admin/user/index.gohtml:20
#: web/templates/admin/surroundings/index.gohtml:83 #: web/templates/admin/surroundings/index.gohtml:83
#: web/templates/admin/amenity/feature/index.gohtml:30 #: web/templates/admin/amenity/feature/index.gohtml:30
@ -1860,7 +1912,7 @@ msgid "New Customer"
msgstr "Nuevo cliente" msgstr "Nuevo cliente"
#: web/templates/admin/customer/form.gohtml:15 #: web/templates/admin/customer/form.gohtml:15
#: web/templates/admin/invoice/index.gohtml:105 #: web/templates/admin/invoice/index.gohtml:106
msgctxt "title" msgctxt "title"
msgid "Customer" msgid "Customer"
msgstr "Cliente" msgstr "Cliente"
@ -1917,19 +1969,19 @@ msgctxt "action"
msgid "Add Customer" msgid "Add Customer"
msgstr "Añadir cliente" msgstr "Añadir cliente"
#: web/templates/admin/customer/index.gohtml:20 #: web/templates/admin/customer/index.gohtml:52
#: web/templates/admin/user/login-attempts.gohtml:20 #: web/templates/admin/user/login-attempts.gohtml:52
#: web/templates/admin/user/index.gohtml:21 #: web/templates/admin/user/index.gohtml:21
msgctxt "header" msgctxt "header"
msgid "Email" msgid "Email"
msgstr "Correo-e" msgstr "Correo-e"
#: web/templates/admin/customer/index.gohtml:21 #: web/templates/admin/customer/index.gohtml:53
msgctxt "header" msgctxt "header"
msgid "Phone" msgid "Phone"
msgstr "Teléfono" msgstr "Teléfono"
#: web/templates/admin/customer/index.gohtml:33 #: web/templates/admin/customer/index.gohtml:61
msgid "No customer found." msgid "No customer found."
msgstr "No se ha encontrado ningún cliente." msgstr "No se ha encontrado ningún cliente."
@ -2047,25 +2099,6 @@ msgstr "Cliente"
msgid "All customers" msgid "All customers"
msgstr "Todos los clientes" msgstr "Todos los clientes"
#: web/templates/admin/invoice/index.gohtml:55
#: web/templates/admin/booking/index.gohtml:38
msgid "All statuses"
msgstr "Todos los estados"
#: web/templates/admin/invoice/index.gohtml:63
#: web/templates/admin/prebooking/index.gohtml:32
#: web/templates/admin/booking/index.gohtml:46
msgctxt "input"
msgid "From date"
msgstr "De la fecha"
#: web/templates/admin/invoice/index.gohtml:72
#: web/templates/admin/prebooking/index.gohtml:41
#: web/templates/admin/booking/index.gohtml:55
msgctxt "input"
msgid "To date"
msgstr "A la fecha"
#: web/templates/admin/invoice/index.gohtml:81 #: web/templates/admin/invoice/index.gohtml:81
msgctxt "input" msgctxt "input"
msgid "Invoice number" msgid "Invoice number"
@ -2076,60 +2109,39 @@ msgctxt "action"
msgid "Filter" msgid "Filter"
msgstr "Filtrar" msgstr "Filtrar"
#: web/templates/admin/invoice/index.gohtml:94
#: web/templates/admin/prebooking/index.gohtml:51
#: web/templates/admin/booking/index.gohtml:65
msgctxt "action"
msgid "Reset"
msgstr "Restablecer"
#: web/templates/admin/invoice/index.gohtml:97 #: web/templates/admin/invoice/index.gohtml:97
msgctxt "action" msgctxt "action"
msgid "Add invoice" msgid "Add invoice"
msgstr "Añadir factura" msgstr "Añadir factura"
#: web/templates/admin/invoice/index.gohtml:102 #: web/templates/admin/invoice/index.gohtml:103
msgctxt "invoice" msgctxt "invoice"
msgid "All" msgid "All"
msgstr "Todas" msgstr "Todas"
#: web/templates/admin/invoice/index.gohtml:104 #: web/templates/admin/invoice/index.gohtml:105
msgctxt "title" msgctxt "title"
msgid "Invoice Num." msgid "Invoice Num."
msgstr "Núm. de factura" msgstr "Núm. de factura"
#: web/templates/admin/invoice/index.gohtml:106 #: web/templates/admin/invoice/index.gohtml:107
msgctxt "title" msgctxt "title"
msgid "Status" msgid "Status"
msgstr "Estado" msgstr "Estado"
#: web/templates/admin/invoice/index.gohtml:107 #: web/templates/admin/invoice/index.gohtml:108
msgctxt "title" msgctxt "title"
msgid "Download" msgid "Download"
msgstr "Descarga" msgstr "Descarga"
#: web/templates/admin/invoice/index.gohtml:108 #: web/templates/admin/invoice/index.gohtml:109
msgctxt "title" msgctxt "title"
msgid "Amount" msgid "Amount"
msgstr "Importe" msgstr "Importe"
#: web/templates/admin/invoice/index.gohtml:115 #: web/templates/admin/invoice/index.gohtml:117
msgctxt "action" msgid "No invoices found."
msgid "Select invoice %v" msgstr "No se ha encontrado ninguna factura."
msgstr "Seleccionar factura %v"
#: web/templates/admin/invoice/index.gohtml:144
msgctxt "action"
msgid "Download invoice %s"
msgstr "Descargar factura %s"
#: web/templates/admin/invoice/index.gohtml:154
msgid "No invoices added yet."
msgstr "No se ha añadido ninguna factura todavía."
#: web/templates/admin/invoice/index.gohtml:161
msgid "Total"
msgstr "Total"
#: web/templates/admin/invoice/view.gohtml:2 #: web/templates/admin/invoice/view.gohtml:2
msgctxt "title" msgctxt "title"
@ -2171,6 +2183,16 @@ msgctxt "title"
msgid "Tax Base" msgid "Tax Base"
msgstr "Base imponible" msgstr "Base imponible"
#: web/templates/admin/invoice/results.gohtml:3
msgctxt "action"
msgid "Select invoice %v"
msgstr "Seleccionar factura %v"
#: web/templates/admin/invoice/results.gohtml:32
msgctxt "action"
msgid "Download invoice %s"
msgstr "Descargar factura %s"
#: web/templates/admin/prebooking/index.gohtml:6 #: web/templates/admin/prebooking/index.gohtml:6
#: web/templates/admin/layout.gohtml:92 #: web/templates/admin/layout.gohtml:92
#: web/templates/admin/booking/form.gohtml:20 #: web/templates/admin/booking/form.gohtml:20
@ -2216,12 +2238,6 @@ msgstr "Nombre del titular"
msgid "No prebooking found." msgid "No prebooking found."
msgstr "No se ha encontrado ninguna prereserva." msgstr "No se ha encontrado ninguna prereserva."
#: web/templates/admin/prebooking/results.gohtml:20
#: web/templates/admin/booking/results.gohtml:23
msgctxt "action"
msgid "Load more"
msgstr "Cargar más"
#: web/templates/admin/login.gohtml:6 web/templates/admin/login.gohtml:18 #: web/templates/admin/login.gohtml:6 web/templates/admin/login.gohtml:18
msgctxt "title" msgctxt "title"
msgid "Login" msgid "Login"
@ -2307,7 +2323,7 @@ msgid "Password Confirmation"
msgstr "Confirmación de la contraseña" msgstr "Confirmación de la contraseña"
#: web/templates/admin/user/login-attempts.gohtml:6 #: web/templates/admin/user/login-attempts.gohtml:6
#: web/templates/admin/user/login-attempts.gohtml:15 #: web/templates/admin/user/login-attempts.gohtml:46
msgctxt "title" msgctxt "title"
msgid "Login Attempts" msgid "Login Attempts"
msgstr "Intentos de entrada" msgstr "Intentos de entrada"
@ -2320,16 +2336,20 @@ msgctxt "title"
msgid "Users" msgid "Users"
msgstr "Usuarios" msgstr "Usuarios"
#: web/templates/admin/user/login-attempts.gohtml:21 #: web/templates/admin/user/login-attempts.gohtml:53
msgctxt "header" msgctxt "header"
msgid "IP Address" msgid "IP Address"
msgstr "Dirección IP" msgstr "Dirección IP"
#: web/templates/admin/user/login-attempts.gohtml:22 #: web/templates/admin/user/login-attempts.gohtml:54
msgctxt "header" msgctxt "header"
msgid "Success" msgid "Success"
msgstr "Éxito" msgstr "Éxito"
#: web/templates/admin/user/login-attempts.gohtml:62
msgid "No logging attempts found."
msgstr "No se ha encontrado ningún intento de entrada."
#: web/templates/admin/user/index.gohtml:14 #: web/templates/admin/user/index.gohtml:14
msgctxt "action" msgctxt "action"
msgid "Add User" msgid "Add User"
@ -2701,7 +2721,7 @@ msgctxt "header"
msgid "Decription" msgid "Decription"
msgstr "Descripción" msgstr "Descripción"
#: web/templates/admin/booking/fields.gohtml:81 pkg/invoice/admin.go:1061 #: web/templates/admin/booking/fields.gohtml:81 pkg/invoice/admin.go:963
#: pkg/booking/cart.go:232 #: pkg/booking/cart.go:232
msgctxt "cart" msgctxt "cart"
msgid "Night" msgid "Night"
@ -2900,7 +2920,7 @@ msgstr "Se ha recibido correctamente el pago de la reserva"
#: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365 #: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365
#: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577 #: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577
#: pkg/campsite/feature.go:269 pkg/season/admin.go:411 #: pkg/campsite/feature.go:269 pkg/season/admin.go:411
#: pkg/invoice/admin.go:1158 pkg/services/admin.go:316 #: pkg/invoice/admin.go:1060 pkg/services/admin.go:316
#: pkg/surroundings/admin.go:340 pkg/amenity/feature.go:269 #: pkg/surroundings/admin.go:340 pkg/amenity/feature.go:269
#: pkg/amenity/admin.go:283 #: pkg/amenity/admin.go:283
msgid "Name can not be empty." msgid "Name can not be empty."
@ -2941,8 +2961,8 @@ msgstr "La imagen de la diapositiva tiene que ser un medio de tipo imagen."
msgid "Email can not be empty." msgid "Email can not be empty."
msgstr "No podéis dejar el correo-e en blanco." msgstr "No podéis dejar el correo-e en blanco."
#: pkg/app/login.go:57 pkg/app/user.go:247 pkg/customer/admin.go:345 #: pkg/app/login.go:57 pkg/app/user.go:247 pkg/customer/admin.go:361
#: pkg/company/admin.go:225 pkg/booking/admin.go:597 pkg/booking/public.go:593 #: pkg/company/admin.go:225 pkg/booking/admin.go:479 pkg/booking/public.go:593
msgid "This email is not valid. It should be like name@domain.com." msgid "This email is not valid. It should be like name@domain.com."
msgstr "Este correo-e no es válido. Tiene que ser parecido a nombre@dominio.com." msgstr "Este correo-e no es válido. Tiene que ser parecido a nombre@dominio.com."
@ -2999,15 +3019,15 @@ msgstr "El valor del máximo tiene que ser un número entero."
msgid "Maximum must be equal or greater than minimum." msgid "Maximum must be equal or greater than minimum."
msgstr "El valor del máximo tiene que ser igual o mayor al del mínimo." msgstr "El valor del máximo tiene que ser igual o mayor al del mínimo."
#: pkg/campsite/types/option.go:382 pkg/invoice/admin.go:1159 #: pkg/campsite/types/option.go:382 pkg/invoice/admin.go:1061
msgid "Price can not be empty." msgid "Price can not be empty."
msgstr "No podéis dejar el precio en blanco." msgstr "No podéis dejar el precio en blanco."
#: pkg/campsite/types/option.go:383 pkg/invoice/admin.go:1160 #: pkg/campsite/types/option.go:383 pkg/invoice/admin.go:1062
msgid "Price must be a decimal number." msgid "Price must be a decimal number."
msgstr "El precio tiene que ser un número decimal." msgstr "El precio tiene que ser un número decimal."
#: pkg/campsite/types/option.go:384 pkg/invoice/admin.go:1161 #: pkg/campsite/types/option.go:384 pkg/invoice/admin.go:1063
msgid "Price must be zero or greater." msgid "Price must be zero or greater."
msgstr "El precio tiene que ser como mínimo cero." msgstr "El precio tiene que ser como mínimo cero."
@ -3153,7 +3173,7 @@ msgctxt "header"
msgid "Children (aged 2 to 10)" msgid "Children (aged 2 to 10)"
msgstr "Niños (de 2 a 10 años)" msgstr "Niños (de 2 a 10 años)"
#: pkg/campsite/admin.go:280 pkg/booking/admin.go:573 pkg/booking/public.go:177 #: pkg/campsite/admin.go:280 pkg/booking/admin.go:455 pkg/booking/public.go:177
#: pkg/booking/public.go:232 #: pkg/booking/public.go:232
msgid "Selected campsite type is not valid." msgid "Selected campsite type is not valid."
msgstr "El tipo de alojamiento escogido no es válido." msgstr "El tipo de alojamiento escogido no es válido."
@ -3191,129 +3211,129 @@ msgstr "No podéis dejar la fecha final en blanco."
msgid "End date must be a valid date." msgid "End date must be a valid date."
msgstr "La fecha final tiene que ser una fecha válida." msgstr "La fecha final tiene que ser una fecha válida."
#: pkg/customer/admin.go:326 pkg/company/admin.go:207 #: pkg/customer/admin.go:342 pkg/company/admin.go:207
#: pkg/booking/checkin.go:300 pkg/booking/public.go:577 #: pkg/booking/checkin.go:300 pkg/booking/public.go:577
msgid "Selected country is not valid." msgid "Selected country is not valid."
msgstr "El país escogido no es válido." msgstr "El país escogido no es válido."
#: pkg/customer/admin.go:330 pkg/booking/checkin.go:284 #: pkg/customer/admin.go:346 pkg/booking/checkin.go:284
msgid "Selected ID document type is not valid." msgid "Selected ID document type is not valid."
msgstr "El tipo de documento de identidad escogido no es válido." msgstr "El tipo de documento de identidad escogido no es válido."
#: pkg/customer/admin.go:331 pkg/booking/checkin.go:285 #: pkg/customer/admin.go:347 pkg/booking/checkin.go:285
msgid "ID document number can not be empty." msgid "ID document number can not be empty."
msgstr "No podéis dejar el número del documento de identidad en blanco." msgstr "No podéis dejar el número del documento de identidad en blanco."
#: pkg/customer/admin.go:333 pkg/booking/checkin.go:291 #: pkg/customer/admin.go:349 pkg/booking/checkin.go:291
#: pkg/booking/checkin.go:292 pkg/booking/admin.go:585 #: pkg/booking/checkin.go:292 pkg/booking/admin.go:467
#: pkg/booking/public.go:581 #: pkg/booking/public.go:581
msgid "Full name can not be empty." msgid "Full name can not be empty."
msgstr "No podéis dejar el nombre y los apellidos en blanco." msgstr "No podéis dejar el nombre y los apellidos en blanco."
#: pkg/customer/admin.go:334 pkg/booking/admin.go:586 pkg/booking/public.go:582 #: pkg/customer/admin.go:350 pkg/booking/admin.go:468 pkg/booking/public.go:582
msgid "Full name must have at least one letter." msgid "Full name must have at least one letter."
msgstr "El nombre y los apellidos tienen que tener como mínimo una letra." msgstr "El nombre y los apellidos tienen que tener como mínimo una letra."
#: pkg/customer/admin.go:337 pkg/company/admin.go:230 pkg/booking/public.go:585 #: pkg/customer/admin.go:353 pkg/company/admin.go:230 pkg/booking/public.go:585
msgid "Address can not be empty." msgid "Address can not be empty."
msgstr "No podéis dejar la dirección en blanco." msgstr "No podéis dejar la dirección en blanco."
#: pkg/customer/admin.go:338 pkg/booking/public.go:586 #: pkg/customer/admin.go:354 pkg/booking/public.go:586
msgid "Town or village can not be empty." msgid "Town or village can not be empty."
msgstr "No podéis dejar la población en blanco." msgstr "No podéis dejar la población en blanco."
#: pkg/customer/admin.go:339 pkg/company/admin.go:233 pkg/booking/public.go:587 #: pkg/customer/admin.go:355 pkg/company/admin.go:233 pkg/booking/public.go:587
msgid "Postcode can not be empty." msgid "Postcode can not be empty."
msgstr "No podéis dejar el código postal en blanco." msgstr "No podéis dejar el código postal en blanco."
#: pkg/customer/admin.go:340 pkg/company/admin.go:234 pkg/booking/admin.go:592 #: pkg/customer/admin.go:356 pkg/company/admin.go:234 pkg/booking/admin.go:474
#: pkg/booking/public.go:588 #: pkg/booking/public.go:588
msgid "This postcode is not valid." msgid "This postcode is not valid."
msgstr "Este código postal no es válido." msgstr "Este código postal no es válido."
#: pkg/customer/admin.go:348 pkg/company/admin.go:220 #: pkg/customer/admin.go:364 pkg/company/admin.go:220
#: pkg/booking/checkin.go:304 pkg/booking/admin.go:602 #: pkg/booking/checkin.go:304 pkg/booking/admin.go:484
#: pkg/booking/public.go:596 #: pkg/booking/public.go:596
msgid "This phone number is not valid." msgid "This phone number is not valid."
msgstr "Este teléfono no es válido." msgstr "Este teléfono no es válido."
#: pkg/invoice/admin.go:679 #: pkg/invoice/admin.go:581
msgctxt "filename" msgctxt "filename"
msgid "invoices.zip" msgid "invoices.zip"
msgstr "facturas.zip" msgstr "facturas.zip"
#: pkg/invoice/admin.go:694 #: pkg/invoice/admin.go:596
msgctxt "filename" msgctxt "filename"
msgid "invoices.ods" msgid "invoices.ods"
msgstr "facturas.ods" msgstr "facturas.ods"
#: pkg/invoice/admin.go:696 pkg/invoice/admin.go:1358 pkg/invoice/admin.go:1365 #: pkg/invoice/admin.go:598 pkg/invoice/admin.go:1260 pkg/invoice/admin.go:1267
msgid "Invalid action" msgid "Invalid action"
msgstr "Acción inválida" msgstr "Acción inválida"
#: pkg/invoice/admin.go:861 #: pkg/invoice/admin.go:763
msgid "Selected invoice status is not valid." msgid "Selected invoice status is not valid."
msgstr "El estado de factura escogida no es válido." msgstr "El estado de factura escogida no es válido."
#: pkg/invoice/admin.go:862 #: pkg/invoice/admin.go:764
msgid "Invoice date can not be empty." msgid "Invoice date can not be empty."
msgstr "No podéis dejar la fecha de factura en blanco." msgstr "No podéis dejar la fecha de factura en blanco."
#: pkg/invoice/admin.go:863 #: pkg/invoice/admin.go:765
msgid "Invoice date must be a valid date." msgid "Invoice date must be a valid date."
msgstr "La fecha de factura tiene que ser una fecha válida." msgstr "La fecha de factura tiene que ser una fecha válida."
#: pkg/invoice/admin.go:1021 #: pkg/invoice/admin.go:923
#, c-format #, c-format
msgid "Re: booking #%s of %s%s" msgid "Re: booking #%s of %s%s"
msgstr "Ref.: reserva núm. %s del %s%s" msgstr "Ref.: reserva núm. %s del %s%s"
#: pkg/invoice/admin.go:1022 #: pkg/invoice/admin.go:924
msgctxt "to_char" msgctxt "to_char"
msgid "MM/DD/YYYY" msgid "MM/DD/YYYY"
msgstr "DD/MM/YYYY" msgstr "DD/MM/YYYY"
#: pkg/invoice/admin.go:1149 #: pkg/invoice/admin.go:1051
msgid "Invoice product ID must be an integer." msgid "Invoice product ID must be an integer."
msgstr "El ID de producto de factura tiene que ser entero." msgstr "El ID de producto de factura tiene que ser entero."
#: pkg/invoice/admin.go:1150 #: pkg/invoice/admin.go:1052
msgid "Invoice product ID one or greater." msgid "Invoice product ID one or greater."
msgstr "El ID de producto de factura tiene que ser como mínimo uno." msgstr "El ID de producto de factura tiene que ser como mínimo uno."
#: pkg/invoice/admin.go:1154 #: pkg/invoice/admin.go:1056
msgid "Product ID must be an integer." msgid "Product ID must be an integer."
msgstr "El ID de producto tiene que ser un número entero." msgstr "El ID de producto tiene que ser un número entero."
#: pkg/invoice/admin.go:1155 #: pkg/invoice/admin.go:1057
msgid "Product ID must zero or greater." msgid "Product ID must zero or greater."
msgstr "El ID de producto tiene que ser como mínimo cero." msgstr "El ID de producto tiene que ser como mínimo cero."
#: pkg/invoice/admin.go:1164 #: pkg/invoice/admin.go:1066
msgid "Quantity can not be empty." msgid "Quantity can not be empty."
msgstr "No podéis dejar la cantidad en blanco." msgstr "No podéis dejar la cantidad en blanco."
#: pkg/invoice/admin.go:1165 #: pkg/invoice/admin.go:1067
msgid "Quantity must be an integer." msgid "Quantity must be an integer."
msgstr "La cantidad tiene que ser un número entero." msgstr "La cantidad tiene que ser un número entero."
#: pkg/invoice/admin.go:1166 #: pkg/invoice/admin.go:1068
msgid "Quantity must one or greater." msgid "Quantity must one or greater."
msgstr "La cantidad tiene que ser como mínimo uno." msgstr "La cantidad tiene que ser como mínimo uno."
#: pkg/invoice/admin.go:1169 #: pkg/invoice/admin.go:1071
msgid "Discount can not be empty." msgid "Discount can not be empty."
msgstr "No podéis dejar el descuento en blanco." msgstr "No podéis dejar el descuento en blanco."
#: pkg/invoice/admin.go:1170 #: pkg/invoice/admin.go:1072
msgid "Discount must be an integer." msgid "Discount must be an integer."
msgstr "El descuento tiene que ser un número entero." msgstr "El descuento tiene que ser un número entero."
#: pkg/invoice/admin.go:1171 pkg/invoice/admin.go:1172 #: pkg/invoice/admin.go:1073 pkg/invoice/admin.go:1074
msgid "Discount must be a percentage between 0 and 100." msgid "Discount must be a percentage between 0 and 100."
msgstr "El descuento tiene que ser un porcentaje entre 1 y 100." msgstr "El descuento tiene que ser un porcentaje entre 1 y 100."
#: pkg/invoice/admin.go:1176 #: pkg/invoice/admin.go:1078
msgid "Selected tax is not valid." msgid "Selected tax is not valid."
msgstr "El impuesto escogido no es válido." msgstr "El impuesto escogido no es válido."
@ -3501,19 +3521,19 @@ msgctxt "filename"
msgid "bookings.ods" msgid "bookings.ods"
msgstr "reservas.ods" msgstr "reservas.ods"
#: pkg/booking/admin.go:591 #: pkg/booking/admin.go:473
msgid "Country can not be empty to validate the postcode." msgid "Country can not be empty to validate the postcode."
msgstr "No podéis dejar el país en blanco para validar el código postal." msgstr "No podéis dejar el país en blanco para validar el código postal."
#: pkg/booking/admin.go:601 #: pkg/booking/admin.go:483
msgid "Country can not be empty to validate the phone." msgid "Country can not be empty to validate the phone."
msgstr "No podéis dejar el país en blanco para validar el teléfono." msgstr "No podéis dejar el país en blanco para validar el teléfono."
#: pkg/booking/admin.go:608 #: pkg/booking/admin.go:490
msgid "You must select at least one accommodation." msgid "You must select at least one accommodation."
msgstr "Tenéis que seleccionar como mínimo un alojamiento." msgstr "Tenéis que seleccionar como mínimo un alojamiento."
#: pkg/booking/admin.go:614 #: pkg/booking/admin.go:496
msgid "The selected accommodations have no available openings in the requested dates." msgid "The selected accommodations have no available openings in the requested dates."
msgstr "Los alojamientos seleccionados no tienen disponibilidad en las fechas pedidas." msgstr "Los alojamientos seleccionados no tienen disponibilidad en las fechas pedidas."
@ -3630,6 +3650,9 @@ msgstr "%s tiene que ser como máximo %d"
msgid "It is mandatory to agree to the reservation conditions." msgid "It is mandatory to agree to the reservation conditions."
msgstr "Es obligatorio aceptar las condiciones de reserva." msgstr "Es obligatorio aceptar las condiciones de reserva."
#~ msgid "Total"
#~ msgstr "Total"
#~ msgid "Select a customer" #~ msgid "Select a customer"
#~ msgstr "Escoja un cliente" #~ msgstr "Escoja un cliente"

277
po/fr.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: camper\n" "Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-05-03 17:19+0200\n" "POT-Creation-Date: 2024-05-13 10:37+0200\n"
"PO-Revision-Date: 2024-02-06 10:05+0100\n" "PO-Revision-Date: 2024-02-06 10:05+0100\n"
"Last-Translator: Oriol Carbonell <info@oriolcarbonell.cat>\n" "Last-Translator: Oriol Carbonell <info@oriolcarbonell.cat>\n"
"Language-Team: French <traduc@traduc.org>\n" "Language-Team: French <traduc@traduc.org>\n"
@ -78,7 +78,7 @@ msgid "Payment"
msgstr "Paiement" msgstr "Paiement"
#: web/templates/mail/payment/details.gotxt:9 #: web/templates/mail/payment/details.gotxt:9
#: web/templates/admin/payment/index.gohtml:21 #: web/templates/admin/payment/index.gohtml:73
#: web/templates/admin/payment/details.gohtml:52 #: web/templates/admin/payment/details.gohtml:52
#: web/templates/admin/prebooking/index.gohtml:59 #: web/templates/admin/prebooking/index.gohtml:59
#: web/templates/admin/booking/index.gohtml:73 #: web/templates/admin/booking/index.gohtml:73
@ -87,7 +87,7 @@ msgid "Reference"
msgstr "Référence" msgstr "Référence"
#: web/templates/mail/payment/details.gotxt:10 #: web/templates/mail/payment/details.gotxt:10
#: web/templates/admin/payment/index.gohtml:22 #: web/templates/admin/payment/index.gohtml:74
#: web/templates/admin/payment/details.gohtml:56 #: web/templates/admin/payment/details.gohtml:56
#: web/templates/admin/booking/index.gohtml:77 #: web/templates/admin/booking/index.gohtml:77
msgctxt "header" msgctxt "header"
@ -131,14 +131,14 @@ msgstr "Préférences de zone"
#: web/templates/mail/payment/details.gotxt:18 #: web/templates/mail/payment/details.gotxt:18
#: web/templates/admin/payment/details.gohtml:82 #: web/templates/admin/payment/details.gohtml:82
msgctxt "input" msgctxt "input"
msgid "ACSI card?" msgid "ACSI / ANWB card?"
msgstr "Carte ACSI ?" msgstr "Carte ACSI / ANWB ?"
#: web/templates/mail/payment/details.gotxt:18 #: web/templates/mail/payment/details.gotxt:18
#: web/templates/admin/payment/details.gohtml:83 #: web/templates/admin/payment/details.gohtml:83
#: web/templates/admin/campsite/type/index.gohtml:53 #: web/templates/admin/campsite/type/index.gohtml:53
#: web/templates/admin/season/index.gohtml:44 #: web/templates/admin/season/index.gohtml:44
#: web/templates/admin/user/login-attempts.gohtml:31 #: web/templates/admin/user/results.gohtml:6
#: web/templates/admin/amenity/index.gohtml:40 #: web/templates/admin/amenity/index.gohtml:40
msgid "Yes" msgid "Yes"
msgstr "Oui" msgstr "Oui"
@ -147,7 +147,7 @@ msgstr "Oui"
#: web/templates/admin/payment/details.gohtml:83 #: web/templates/admin/payment/details.gohtml:83
#: web/templates/admin/campsite/type/index.gohtml:53 #: web/templates/admin/campsite/type/index.gohtml:53
#: web/templates/admin/season/index.gohtml:44 #: web/templates/admin/season/index.gohtml:44
#: web/templates/admin/user/login-attempts.gohtml:31 #: web/templates/admin/user/results.gohtml:6
#: web/templates/admin/amenity/index.gohtml:40 #: web/templates/admin/amenity/index.gohtml:40
msgid "No" msgid "No"
msgstr "Non" msgstr "Non"
@ -179,7 +179,7 @@ msgstr "Nuits"
#: web/templates/mail/payment/details.gotxt:22 #: web/templates/mail/payment/details.gotxt:22
#: web/templates/public/booking/fields.gohtml:60 #: web/templates/public/booking/fields.gohtml:60
#: web/templates/admin/payment/details.gohtml:98 #: web/templates/admin/payment/details.gohtml:98
#: web/templates/admin/booking/fields.gohtml:93 pkg/invoice/admin.go:1062 #: web/templates/admin/booking/fields.gohtml:93 pkg/invoice/admin.go:964
msgctxt "input" msgctxt "input"
msgid "Adults aged 17 or older" msgid "Adults aged 17 or older"
msgstr "Adultes âgés 17 ans ou plus" msgstr "Adultes âgés 17 ans ou plus"
@ -187,7 +187,7 @@ msgstr "Adultes âgés 17 ans ou plus"
#: web/templates/mail/payment/details.gotxt:23 #: web/templates/mail/payment/details.gotxt:23
#: web/templates/public/booking/fields.gohtml:71 #: web/templates/public/booking/fields.gohtml:71
#: web/templates/admin/payment/details.gohtml:102 #: web/templates/admin/payment/details.gohtml:102
#: web/templates/admin/booking/fields.gohtml:109 pkg/invoice/admin.go:1063 #: web/templates/admin/booking/fields.gohtml:109 pkg/invoice/admin.go:965
msgctxt "input" msgctxt "input"
msgid "Teenagers from 11 to 16 years old" msgid "Teenagers from 11 to 16 years old"
msgstr "Adolescents de 11 à 16 ans" msgstr "Adolescents de 11 à 16 ans"
@ -195,7 +195,7 @@ msgstr "Adolescents de 11 à 16 ans"
#: web/templates/mail/payment/details.gotxt:24 #: web/templates/mail/payment/details.gotxt:24
#: web/templates/public/booking/fields.gohtml:82 #: web/templates/public/booking/fields.gohtml:82
#: web/templates/admin/payment/details.gohtml:106 #: web/templates/admin/payment/details.gohtml:106
#: web/templates/admin/booking/fields.gohtml:125 pkg/invoice/admin.go:1064 #: web/templates/admin/booking/fields.gohtml:125 pkg/invoice/admin.go:966
msgctxt "input" msgctxt "input"
msgid "Children from 2 to 10 years old" msgid "Children from 2 to 10 years old"
msgstr "Enfants de 2 à 10 ans" msgstr "Enfants de 2 à 10 ans"
@ -203,14 +203,14 @@ msgstr "Enfants de 2 à 10 ans"
#: web/templates/mail/payment/details.gotxt:25 #: web/templates/mail/payment/details.gotxt:25
#: web/templates/public/booking/fields.gohtml:100 #: web/templates/public/booking/fields.gohtml:100
#: web/templates/admin/payment/details.gohtml:110 #: web/templates/admin/payment/details.gohtml:110
#: web/templates/admin/booking/fields.gohtml:140 pkg/invoice/admin.go:1065 #: web/templates/admin/booking/fields.gohtml:140 pkg/invoice/admin.go:967
msgctxt "input" msgctxt "input"
msgid "Dogs" msgid "Dogs"
msgstr "Chiens" msgstr "Chiens"
#: web/templates/mail/payment/details.gotxt:26 #: web/templates/mail/payment/details.gotxt:26
#: web/templates/admin/payment/details.gohtml:114 #: web/templates/admin/payment/details.gohtml:114
#: web/templates/admin/booking/fields.gohtml:167 pkg/invoice/admin.go:1066 #: web/templates/admin/booking/fields.gohtml:167 pkg/invoice/admin.go:968
#: pkg/booking/cart.go:242 #: pkg/booking/cart.go:242
msgctxt "cart" msgctxt "cart"
msgid "Tourist tax" msgid "Tourist tax"
@ -293,6 +293,7 @@ msgstr "Pays"
#: web/templates/mail/payment/details.gotxt:46 #: web/templates/mail/payment/details.gotxt:46
#: web/templates/public/booking/fields.gohtml:201 #: web/templates/public/booking/fields.gohtml:201
#: web/templates/admin/payment/details.gohtml:163 #: web/templates/admin/payment/details.gohtml:163
#: web/templates/admin/customer/index.gohtml:33
#: web/templates/admin/login.gohtml:27 web/templates/admin/profile.gohtml:38 #: web/templates/admin/login.gohtml:27 web/templates/admin/profile.gohtml:38
#: web/templates/admin/taxDetails.gohtml:53 #: web/templates/admin/taxDetails.gohtml:53
msgctxt "input" msgctxt "input"
@ -418,7 +419,7 @@ msgid "Order Number"
msgstr "Numéro de commande" msgstr "Numéro de commande"
#: web/templates/public/payment/details.gohtml:8 #: web/templates/public/payment/details.gohtml:8
#: web/templates/admin/invoice/index.gohtml:103 #: web/templates/admin/invoice/index.gohtml:104
#: web/templates/admin/invoice/view.gohtml:26 #: web/templates/admin/invoice/view.gohtml:26
msgctxt "title" msgctxt "title"
msgid "Date" msgid "Date"
@ -596,6 +597,12 @@ msgctxt "action"
msgid "Filters" msgid "Filters"
msgstr "Filtres" msgstr "Filtres"
#: web/templates/public/form.gohtml:93 web/templates/admin/form.gohtml:93
#: web/templates/admin/prebooking/results.gohtml:20
msgctxt "action"
msgid "Load more"
msgstr "Charger plus"
#: web/templates/public/campsite/type.gohtml:49 #: web/templates/public/campsite/type.gohtml:49
#: web/templates/public/booking/fields.gohtml:278 #: web/templates/public/booking/fields.gohtml:278
msgctxt "action" msgctxt "action"
@ -1036,8 +1043,8 @@ msgstr "Choisissez un pays"
#: web/templates/public/booking/fields.gohtml:247 #: web/templates/public/booking/fields.gohtml:247
#: web/templates/admin/booking/fields.gohtml:259 #: web/templates/admin/booking/fields.gohtml:259
msgctxt "input" msgctxt "input"
msgid "ACSI card? (optional)" msgid "ACSI / ANWB card? (optional)"
msgstr "Carte ACSI ? (facultatif)" msgstr "Carte ACSI / ANWB ? (facultatif)"
#: web/templates/public/booking/fields.gohtml:255 #: web/templates/public/booking/fields.gohtml:255
msgctxt "input" msgctxt "input"
@ -1046,7 +1053,7 @@ msgstr "Jai lu et jaccepte %[1]sles conditions de réservation%[2]s"
#: web/templates/public/booking/fields.gohtml:263 #: web/templates/public/booking/fields.gohtml:263
msgid "By down paying the %d %% of the total, you are pre-booking your preferences. We will respond within 24 hours and this percentage will be charged if accepted." msgid "By down paying the %d %% of the total, you are pre-booking your preferences. We will respond within 24 hours and this percentage will be charged if accepted."
msgstr "En En effectuant le paiement de %d %% du total vous pré-réservez vos préférences. Nous vous répondrons dans les 24 heures et ce pourcentage sera facturé en cas dacceptation." msgstr "En effectuant le paiement de %d %% du total vous pré-réservez vos préférences. Nous vous répondrons dans les 24 heures et ce pourcentage sera facturé en cas dacceptation."
#: web/templates/public/booking/fields.gohtml:265 #: web/templates/public/booking/fields.gohtml:265
msgid "By paying the total you are pre-booking your preferences. We will respond within 24 hours and this amount will be charged if accepted." msgid "By paying the total you are pre-booking your preferences. We will respond within 24 hours and this amount will be charged if accepted."
@ -1107,25 +1114,69 @@ msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Enregistrer les changements" msgstr "Enregistrer les changements"
#: web/templates/admin/payment/index.gohtml:20 #: web/templates/admin/payment/index.gohtml:24
#: web/templates/admin/user/login-attempts.gohtml:19 msgctxt "input"
msgid "Payment status"
msgstr "Statut du paiement"
#: web/templates/admin/payment/index.gohtml:28
#: web/templates/admin/invoice/index.gohtml:55
#: web/templates/admin/booking/index.gohtml:38
msgid "All statuses"
msgstr "Tous les statuts"
#: web/templates/admin/payment/index.gohtml:36
#: web/templates/admin/invoice/index.gohtml:63
#: web/templates/admin/prebooking/index.gohtml:32
#: web/templates/admin/user/login-attempts.gohtml:24
#: web/templates/admin/booking/index.gohtml:46
msgctxt "input"
msgid "From date"
msgstr "Partir de la date"
#: web/templates/admin/payment/index.gohtml:45
#: web/templates/admin/invoice/index.gohtml:72
#: web/templates/admin/prebooking/index.gohtml:41
#: web/templates/admin/user/login-attempts.gohtml:33
#: web/templates/admin/booking/index.gohtml:55
msgctxt "input"
msgid "To date"
msgstr "À ce jour"
#: web/templates/admin/payment/index.gohtml:54
msgctxt "input"
msgid "Reference"
msgstr "Référence"
#: web/templates/admin/payment/index.gohtml:64
#: web/templates/admin/customer/index.gohtml:43
#: web/templates/admin/invoice/index.gohtml:94
#: web/templates/admin/prebooking/index.gohtml:51
#: web/templates/admin/user/login-attempts.gohtml:43
#: web/templates/admin/booking/index.gohtml:65
msgctxt "action"
msgid "Reset"
msgstr "Réinitialiser"
#: web/templates/admin/payment/index.gohtml:72
#: web/templates/admin/user/login-attempts.gohtml:51
msgctxt "header" msgctxt "header"
msgid "Date" msgid "Date"
msgstr "Date" msgstr "Date"
#: web/templates/admin/payment/index.gohtml:23 #: web/templates/admin/payment/index.gohtml:75
msgctxt "header" msgctxt "header"
msgid "Down payment" msgid "Down payment"
msgstr "Acompte" msgstr "Acompte"
#: web/templates/admin/payment/index.gohtml:24 #: web/templates/admin/payment/index.gohtml:76
#: web/templates/admin/booking/fields.gohtml:75 #: web/templates/admin/booking/fields.gohtml:75
#: web/templates/admin/booking/fields.gohtml:173 #: web/templates/admin/booking/fields.gohtml:173
msgctxt "header" msgctxt "header"
msgid "Total" msgid "Total"
msgstr "Totale" msgstr "Totale"
#: web/templates/admin/payment/index.gohtml:40 #: web/templates/admin/payment/index.gohtml:84
msgid "No payments found." msgid "No payments found."
msgstr "Aucun paiement trouvée." msgstr "Aucun paiement trouvée."
@ -1183,6 +1234,7 @@ msgstr "Slug"
#: web/templates/admin/campsite/type/form.gohtml:51 #: web/templates/admin/campsite/type/form.gohtml:51
#: web/templates/admin/campsite/type/option/form.gohtml:41 #: web/templates/admin/campsite/type/option/form.gohtml:41
#: web/templates/admin/season/form.gohtml:50 #: web/templates/admin/season/form.gohtml:50
#: web/templates/admin/customer/index.gohtml:24
#: web/templates/admin/invoice/product-form.gohtml:16 #: web/templates/admin/invoice/product-form.gohtml:16
#: web/templates/admin/services/form.gohtml:53 #: web/templates/admin/services/form.gohtml:53
#: web/templates/admin/profile.gohtml:29 #: web/templates/admin/profile.gohtml:29
@ -1255,7 +1307,7 @@ msgstr "Ajouter un texte juridique"
#: web/templates/admin/campsite/type/option/index.gohtml:30 #: web/templates/admin/campsite/type/option/index.gohtml:30
#: web/templates/admin/campsite/type/index.gohtml:29 #: web/templates/admin/campsite/type/index.gohtml:29
#: web/templates/admin/season/index.gohtml:29 #: web/templates/admin/season/index.gohtml:29
#: web/templates/admin/customer/index.gohtml:19 #: web/templates/admin/customer/index.gohtml:51
#: web/templates/admin/user/index.gohtml:20 #: web/templates/admin/user/index.gohtml:20
#: web/templates/admin/surroundings/index.gohtml:83 #: web/templates/admin/surroundings/index.gohtml:83
#: web/templates/admin/amenity/feature/index.gohtml:30 #: web/templates/admin/amenity/feature/index.gohtml:30
@ -1860,7 +1912,7 @@ msgid "New Customer"
msgstr "Nouveau client" msgstr "Nouveau client"
#: web/templates/admin/customer/form.gohtml:15 #: web/templates/admin/customer/form.gohtml:15
#: web/templates/admin/invoice/index.gohtml:105 #: web/templates/admin/invoice/index.gohtml:106
msgctxt "title" msgctxt "title"
msgid "Customer" msgid "Customer"
msgstr "Client" msgstr "Client"
@ -1917,19 +1969,19 @@ msgctxt "action"
msgid "Add Customer" msgid "Add Customer"
msgstr "Ajouter un client" msgstr "Ajouter un client"
#: web/templates/admin/customer/index.gohtml:20 #: web/templates/admin/customer/index.gohtml:52
#: web/templates/admin/user/login-attempts.gohtml:20 #: web/templates/admin/user/login-attempts.gohtml:52
#: web/templates/admin/user/index.gohtml:21 #: web/templates/admin/user/index.gohtml:21
msgctxt "header" msgctxt "header"
msgid "Email" msgid "Email"
msgstr "E-mail" msgstr "E-mail"
#: web/templates/admin/customer/index.gohtml:21 #: web/templates/admin/customer/index.gohtml:53
msgctxt "header" msgctxt "header"
msgid "Phone" msgid "Phone"
msgstr "Téléphone" msgstr "Téléphone"
#: web/templates/admin/customer/index.gohtml:33 #: web/templates/admin/customer/index.gohtml:61
msgid "No customer found." msgid "No customer found."
msgstr "Aucun client trouvée." msgstr "Aucun client trouvée."
@ -2047,25 +2099,6 @@ msgstr "Client"
msgid "All customers" msgid "All customers"
msgstr "Tous les clients" msgstr "Tous les clients"
#: web/templates/admin/invoice/index.gohtml:55
#: web/templates/admin/booking/index.gohtml:38
msgid "All statuses"
msgstr "Tous les statuts"
#: web/templates/admin/invoice/index.gohtml:63
#: web/templates/admin/prebooking/index.gohtml:32
#: web/templates/admin/booking/index.gohtml:46
msgctxt "input"
msgid "From date"
msgstr "Partir de la date"
#: web/templates/admin/invoice/index.gohtml:72
#: web/templates/admin/prebooking/index.gohtml:41
#: web/templates/admin/booking/index.gohtml:55
msgctxt "input"
msgid "To date"
msgstr "À ce jour"
#: web/templates/admin/invoice/index.gohtml:81 #: web/templates/admin/invoice/index.gohtml:81
msgctxt "input" msgctxt "input"
msgid "Invoice number" msgid "Invoice number"
@ -2076,60 +2109,39 @@ msgctxt "action"
msgid "Filter" msgid "Filter"
msgstr "Filtrer" msgstr "Filtrer"
#: web/templates/admin/invoice/index.gohtml:94
#: web/templates/admin/prebooking/index.gohtml:51
#: web/templates/admin/booking/index.gohtml:65
msgctxt "action"
msgid "Reset"
msgstr "Réinitialiser"
#: web/templates/admin/invoice/index.gohtml:97 #: web/templates/admin/invoice/index.gohtml:97
msgctxt "action" msgctxt "action"
msgid "Add invoice" msgid "Add invoice"
msgstr "Nouvelle facture" msgstr "Nouvelle facture"
#: web/templates/admin/invoice/index.gohtml:102 #: web/templates/admin/invoice/index.gohtml:103
msgctxt "invoice" msgctxt "invoice"
msgid "All" msgid "All"
msgstr "Toutes" msgstr "Toutes"
#: web/templates/admin/invoice/index.gohtml:104 #: web/templates/admin/invoice/index.gohtml:105
msgctxt "title" msgctxt "title"
msgid "Invoice Num." msgid "Invoice Num."
msgstr "Num. de facture" msgstr "Num. de facture"
#: web/templates/admin/invoice/index.gohtml:106 #: web/templates/admin/invoice/index.gohtml:107
msgctxt "title" msgctxt "title"
msgid "Status" msgid "Status"
msgstr "Statut" msgstr "Statut"
#: web/templates/admin/invoice/index.gohtml:107 #: web/templates/admin/invoice/index.gohtml:108
msgctxt "title" msgctxt "title"
msgid "Download" msgid "Download"
msgstr "Téléchargement" msgstr "Téléchargement"
#: web/templates/admin/invoice/index.gohtml:108 #: web/templates/admin/invoice/index.gohtml:109
msgctxt "title" msgctxt "title"
msgid "Amount" msgid "Amount"
msgstr "Import" msgstr "Import"
#: web/templates/admin/invoice/index.gohtml:115 #: web/templates/admin/invoice/index.gohtml:117
msgctxt "action" msgid "No invoices found."
msgid "Select invoice %v" msgstr "Aucune facture trouvée."
msgstr "Sélectionner la facture %v"
#: web/templates/admin/invoice/index.gohtml:144
msgctxt "action"
msgid "Download invoice %s"
msgstr "Télécharger la facture %s"
#: web/templates/admin/invoice/index.gohtml:154
msgid "No invoices added yet."
msgstr "Aucune facture na encore été ajouté."
#: web/templates/admin/invoice/index.gohtml:161
msgid "Total"
msgstr "Totale"
#: web/templates/admin/invoice/view.gohtml:2 #: web/templates/admin/invoice/view.gohtml:2
msgctxt "title" msgctxt "title"
@ -2171,6 +2183,16 @@ msgctxt "title"
msgid "Tax Base" msgid "Tax Base"
msgstr "Import imposable" msgstr "Import imposable"
#: web/templates/admin/invoice/results.gohtml:3
msgctxt "action"
msgid "Select invoice %v"
msgstr "Sélectionner la facture %v"
#: web/templates/admin/invoice/results.gohtml:32
msgctxt "action"
msgid "Download invoice %s"
msgstr "Télécharger la facture %s"
#: web/templates/admin/prebooking/index.gohtml:6 #: web/templates/admin/prebooking/index.gohtml:6
#: web/templates/admin/layout.gohtml:92 #: web/templates/admin/layout.gohtml:92
#: web/templates/admin/booking/form.gohtml:20 #: web/templates/admin/booking/form.gohtml:20
@ -2216,12 +2238,6 @@ msgstr "Nom du titulaire"
msgid "No prebooking found." msgid "No prebooking found."
msgstr "Aucune pré-réservation trouvée." msgstr "Aucune pré-réservation trouvée."
#: web/templates/admin/prebooking/results.gohtml:20
#: web/templates/admin/booking/results.gohtml:23
msgctxt "action"
msgid "Load more"
msgstr "Charger plus"
#: web/templates/admin/login.gohtml:6 web/templates/admin/login.gohtml:18 #: web/templates/admin/login.gohtml:6 web/templates/admin/login.gohtml:18
msgctxt "title" msgctxt "title"
msgid "Login" msgid "Login"
@ -2307,7 +2323,7 @@ msgid "Password Confirmation"
msgstr "Confirmation du mot de passe" msgstr "Confirmation du mot de passe"
#: web/templates/admin/user/login-attempts.gohtml:6 #: web/templates/admin/user/login-attempts.gohtml:6
#: web/templates/admin/user/login-attempts.gohtml:15 #: web/templates/admin/user/login-attempts.gohtml:46
msgctxt "title" msgctxt "title"
msgid "Login Attempts" msgid "Login Attempts"
msgstr "Tentatives de connexion" msgstr "Tentatives de connexion"
@ -2320,16 +2336,20 @@ msgctxt "title"
msgid "Users" msgid "Users"
msgstr "Utilisateurs" msgstr "Utilisateurs"
#: web/templates/admin/user/login-attempts.gohtml:21 #: web/templates/admin/user/login-attempts.gohtml:53
msgctxt "header" msgctxt "header"
msgid "IP Address" msgid "IP Address"
msgstr "Adresse IP" msgstr "Adresse IP"
#: web/templates/admin/user/login-attempts.gohtml:22 #: web/templates/admin/user/login-attempts.gohtml:54
msgctxt "header" msgctxt "header"
msgid "Success" msgid "Success"
msgstr "Succès" msgstr "Succès"
#: web/templates/admin/user/login-attempts.gohtml:62
msgid "No logging attempts found."
msgstr "Aucune tentative de journalisation trouvée."
#: web/templates/admin/user/index.gohtml:14 #: web/templates/admin/user/index.gohtml:14
msgctxt "action" msgctxt "action"
msgid "Add User" msgid "Add User"
@ -2701,7 +2721,7 @@ msgctxt "header"
msgid "Decription" msgid "Decription"
msgstr "Description" msgstr "Description"
#: web/templates/admin/booking/fields.gohtml:81 pkg/invoice/admin.go:1061 #: web/templates/admin/booking/fields.gohtml:81 pkg/invoice/admin.go:963
#: pkg/booking/cart.go:232 #: pkg/booking/cart.go:232
msgctxt "cart" msgctxt "cart"
msgid "Night" msgid "Night"
@ -2900,7 +2920,7 @@ msgstr "Paiement de réservation reçu avec succès"
#: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365 #: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365
#: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577 #: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577
#: pkg/campsite/feature.go:269 pkg/season/admin.go:411 #: pkg/campsite/feature.go:269 pkg/season/admin.go:411
#: pkg/invoice/admin.go:1158 pkg/services/admin.go:316 #: pkg/invoice/admin.go:1060 pkg/services/admin.go:316
#: pkg/surroundings/admin.go:340 pkg/amenity/feature.go:269 #: pkg/surroundings/admin.go:340 pkg/amenity/feature.go:269
#: pkg/amenity/admin.go:283 #: pkg/amenity/admin.go:283
msgid "Name can not be empty." msgid "Name can not be empty."
@ -2941,8 +2961,8 @@ msgstr "Limage de la diapositive doit être de type média dimage."
msgid "Email can not be empty." msgid "Email can not be empty."
msgstr "Le-mail ne peut pas être vide." msgstr "Le-mail ne peut pas être vide."
#: pkg/app/login.go:57 pkg/app/user.go:247 pkg/customer/admin.go:345 #: pkg/app/login.go:57 pkg/app/user.go:247 pkg/customer/admin.go:361
#: pkg/company/admin.go:225 pkg/booking/admin.go:597 pkg/booking/public.go:593 #: pkg/company/admin.go:225 pkg/booking/admin.go:479 pkg/booking/public.go:593
msgid "This email is not valid. It should be like name@domain.com." msgid "This email is not valid. It should be like name@domain.com."
msgstr "Cette adresse e-mail nest pas valide. Il devrait en être name@domain.com." msgstr "Cette adresse e-mail nest pas valide. Il devrait en être name@domain.com."
@ -2999,15 +3019,15 @@ msgstr "Le maximum doit être un nombre entier."
msgid "Maximum must be equal or greater than minimum." msgid "Maximum must be equal or greater than minimum."
msgstr "Le maximum doit être égal ou supérieur au minimum." msgstr "Le maximum doit être égal ou supérieur au minimum."
#: pkg/campsite/types/option.go:382 pkg/invoice/admin.go:1159 #: pkg/campsite/types/option.go:382 pkg/invoice/admin.go:1061
msgid "Price can not be empty." msgid "Price can not be empty."
msgstr "Le prix ne peut pas être vide." msgstr "Le prix ne peut pas être vide."
#: pkg/campsite/types/option.go:383 pkg/invoice/admin.go:1160 #: pkg/campsite/types/option.go:383 pkg/invoice/admin.go:1062
msgid "Price must be a decimal number." msgid "Price must be a decimal number."
msgstr "Le prix doit être un nombre décimal." msgstr "Le prix doit être un nombre décimal."
#: pkg/campsite/types/option.go:384 pkg/invoice/admin.go:1161 #: pkg/campsite/types/option.go:384 pkg/invoice/admin.go:1063
msgid "Price must be zero or greater." msgid "Price must be zero or greater."
msgstr "Le prix doit être égal ou supérieur à zéro." msgstr "Le prix doit être égal ou supérieur à zéro."
@ -3153,7 +3173,7 @@ msgctxt "header"
msgid "Children (aged 2 to 10)" msgid "Children (aged 2 to 10)"
msgstr "Enfants (de 2 à 10 anys)" msgstr "Enfants (de 2 à 10 anys)"
#: pkg/campsite/admin.go:280 pkg/booking/admin.go:573 pkg/booking/public.go:177 #: pkg/campsite/admin.go:280 pkg/booking/admin.go:455 pkg/booking/public.go:177
#: pkg/booking/public.go:232 #: pkg/booking/public.go:232
msgid "Selected campsite type is not valid." msgid "Selected campsite type is not valid."
msgstr "Le type demplacement sélectionné nest pas valide." msgstr "Le type demplacement sélectionné nest pas valide."
@ -3191,129 +3211,129 @@ msgstr "La date de fin ne peut pas être vide."
msgid "End date must be a valid date." msgid "End date must be a valid date."
msgstr "La date de fin doit être une date valide." msgstr "La date de fin doit être une date valide."
#: pkg/customer/admin.go:326 pkg/company/admin.go:207 #: pkg/customer/admin.go:342 pkg/company/admin.go:207
#: pkg/booking/checkin.go:300 pkg/booking/public.go:577 #: pkg/booking/checkin.go:300 pkg/booking/public.go:577
msgid "Selected country is not valid." msgid "Selected country is not valid."
msgstr "Le pays sélectionné nest pas valide." msgstr "Le pays sélectionné nest pas valide."
#: pkg/customer/admin.go:330 pkg/booking/checkin.go:284 #: pkg/customer/admin.go:346 pkg/booking/checkin.go:284
msgid "Selected ID document type is not valid." msgid "Selected ID document type is not valid."
msgstr "Le type de document didentité sélectionné nest pas valide." msgstr "Le type de document didentité sélectionné nest pas valide."
#: pkg/customer/admin.go:331 pkg/booking/checkin.go:285 #: pkg/customer/admin.go:347 pkg/booking/checkin.go:285
msgid "ID document number can not be empty." msgid "ID document number can not be empty."
msgstr "Le numéro de documento didentité ne peut pas être vide." msgstr "Le numéro de documento didentité ne peut pas être vide."
#: pkg/customer/admin.go:333 pkg/booking/checkin.go:291 #: pkg/customer/admin.go:349 pkg/booking/checkin.go:291
#: pkg/booking/checkin.go:292 pkg/booking/admin.go:585 #: pkg/booking/checkin.go:292 pkg/booking/admin.go:467
#: pkg/booking/public.go:581 #: pkg/booking/public.go:581
msgid "Full name can not be empty." msgid "Full name can not be empty."
msgstr "Le nom complet ne peut pas être vide." msgstr "Le nom complet ne peut pas être vide."
#: pkg/customer/admin.go:334 pkg/booking/admin.go:586 pkg/booking/public.go:582 #: pkg/customer/admin.go:350 pkg/booking/admin.go:468 pkg/booking/public.go:582
msgid "Full name must have at least one letter." msgid "Full name must have at least one letter."
msgstr "Le nom complet doit comporter au moins une lettre." msgstr "Le nom complet doit comporter au moins une lettre."
#: pkg/customer/admin.go:337 pkg/company/admin.go:230 pkg/booking/public.go:585 #: pkg/customer/admin.go:353 pkg/company/admin.go:230 pkg/booking/public.go:585
msgid "Address can not be empty." msgid "Address can not be empty."
msgstr "Ladresse ne peut pas être vide." msgstr "Ladresse ne peut pas être vide."
#: pkg/customer/admin.go:338 pkg/booking/public.go:586 #: pkg/customer/admin.go:354 pkg/booking/public.go:586
msgid "Town or village can not be empty." msgid "Town or village can not be empty."
msgstr "La ville ne peut pas être vide." msgstr "La ville ne peut pas être vide."
#: pkg/customer/admin.go:339 pkg/company/admin.go:233 pkg/booking/public.go:587 #: pkg/customer/admin.go:355 pkg/company/admin.go:233 pkg/booking/public.go:587
msgid "Postcode can not be empty." msgid "Postcode can not be empty."
msgstr "Le code postal ne peut pas être vide." msgstr "Le code postal ne peut pas être vide."
#: pkg/customer/admin.go:340 pkg/company/admin.go:234 pkg/booking/admin.go:592 #: pkg/customer/admin.go:356 pkg/company/admin.go:234 pkg/booking/admin.go:474
#: pkg/booking/public.go:588 #: pkg/booking/public.go:588
msgid "This postcode is not valid." msgid "This postcode is not valid."
msgstr "Ce code postal nest pas valide." msgstr "Ce code postal nest pas valide."
#: pkg/customer/admin.go:348 pkg/company/admin.go:220 #: pkg/customer/admin.go:364 pkg/company/admin.go:220
#: pkg/booking/checkin.go:304 pkg/booking/admin.go:602 #: pkg/booking/checkin.go:304 pkg/booking/admin.go:484
#: pkg/booking/public.go:596 #: pkg/booking/public.go:596
msgid "This phone number is not valid." msgid "This phone number is not valid."
msgstr "Ce numéro de téléphone nest pas valide." msgstr "Ce numéro de téléphone nest pas valide."
#: pkg/invoice/admin.go:679 #: pkg/invoice/admin.go:581
msgctxt "filename" msgctxt "filename"
msgid "invoices.zip" msgid "invoices.zip"
msgstr "factures.zip" msgstr "factures.zip"
#: pkg/invoice/admin.go:694 #: pkg/invoice/admin.go:596
msgctxt "filename" msgctxt "filename"
msgid "invoices.ods" msgid "invoices.ods"
msgstr "factures.ods" msgstr "factures.ods"
#: pkg/invoice/admin.go:696 pkg/invoice/admin.go:1358 pkg/invoice/admin.go:1365 #: pkg/invoice/admin.go:598 pkg/invoice/admin.go:1260 pkg/invoice/admin.go:1267
msgid "Invalid action" msgid "Invalid action"
msgstr "Actin invalide" msgstr "Actin invalide"
#: pkg/invoice/admin.go:861 #: pkg/invoice/admin.go:763
msgid "Selected invoice status is not valid." msgid "Selected invoice status is not valid."
msgstr "Lstatut sélectionné nest pas valide." msgstr "Lstatut sélectionné nest pas valide."
#: pkg/invoice/admin.go:862 #: pkg/invoice/admin.go:764
msgid "Invoice date can not be empty." msgid "Invoice date can not be empty."
msgstr "La date de facture ne peut pas être vide." msgstr "La date de facture ne peut pas être vide."
#: pkg/invoice/admin.go:863 #: pkg/invoice/admin.go:765
msgid "Invoice date must be a valid date." msgid "Invoice date must be a valid date."
msgstr "La date de facture doit être une date valide." msgstr "La date de facture doit être une date valide."
#: pkg/invoice/admin.go:1021 #: pkg/invoice/admin.go:923
#, c-format #, c-format
msgid "Re: booking #%s of %s%s" msgid "Re: booking #%s of %s%s"
msgstr "Réf. : réservation num. %s du %s%s" msgstr "Réf. : réservation num. %s du %s%s"
#: pkg/invoice/admin.go:1022 #: pkg/invoice/admin.go:924
msgctxt "to_char" msgctxt "to_char"
msgid "MM/DD/YYYY" msgid "MM/DD/YYYY"
msgstr "DD/MM/YYYY" msgstr "DD/MM/YYYY"
#: pkg/invoice/admin.go:1149 #: pkg/invoice/admin.go:1051
msgid "Invoice product ID must be an integer." msgid "Invoice product ID must be an integer."
msgstr "Le ID de produit de facture doit être un entier." msgstr "Le ID de produit de facture doit être un entier."
#: pkg/invoice/admin.go:1150 #: pkg/invoice/admin.go:1052
msgid "Invoice product ID one or greater." msgid "Invoice product ID one or greater."
msgstr "Le ID de produit de facture doit être égal ou supérieur à un." msgstr "Le ID de produit de facture doit être égal ou supérieur à un."
#: pkg/invoice/admin.go:1154 #: pkg/invoice/admin.go:1056
msgid "Product ID must be an integer." msgid "Product ID must be an integer."
msgstr "Le ID de produit doit être un entier." msgstr "Le ID de produit doit être un entier."
#: pkg/invoice/admin.go:1155 #: pkg/invoice/admin.go:1057
msgid "Product ID must zero or greater." msgid "Product ID must zero or greater."
msgstr "Le ID de produit doit être égal ou supérieur à zéro." msgstr "Le ID de produit doit être égal ou supérieur à zéro."
#: pkg/invoice/admin.go:1164 #: pkg/invoice/admin.go:1066
msgid "Quantity can not be empty." msgid "Quantity can not be empty."
msgstr "La quantité ne peut pas être vide." msgstr "La quantité ne peut pas être vide."
#: pkg/invoice/admin.go:1165 #: pkg/invoice/admin.go:1067
msgid "Quantity must be an integer." msgid "Quantity must be an integer."
msgstr "La quantité doit être un entier." msgstr "La quantité doit être un entier."
#: pkg/invoice/admin.go:1166 #: pkg/invoice/admin.go:1068
msgid "Quantity must one or greater." msgid "Quantity must one or greater."
msgstr "La quantité doit être égnal ou supérieur à zéro." msgstr "La quantité doit être égnal ou supérieur à zéro."
#: pkg/invoice/admin.go:1169 #: pkg/invoice/admin.go:1071
msgid "Discount can not be empty." msgid "Discount can not be empty."
msgstr "Le rabais ne peut pas être vide." msgstr "Le rabais ne peut pas être vide."
#: pkg/invoice/admin.go:1170 #: pkg/invoice/admin.go:1072
msgid "Discount must be an integer." msgid "Discount must be an integer."
msgstr "Le rabais doit être un entier." msgstr "Le rabais doit être un entier."
#: pkg/invoice/admin.go:1171 pkg/invoice/admin.go:1172 #: pkg/invoice/admin.go:1073 pkg/invoice/admin.go:1074
msgid "Discount must be a percentage between 0 and 100." msgid "Discount must be a percentage between 0 and 100."
msgstr "Le rabais doit être un pourcentage compris entre 0 et 100." msgstr "Le rabais doit être un pourcentage compris entre 0 et 100."
#: pkg/invoice/admin.go:1176 #: pkg/invoice/admin.go:1078
msgid "Selected tax is not valid." msgid "Selected tax is not valid."
msgstr "La taxe sélectionnée nest pas valide." msgstr "La taxe sélectionnée nest pas valide."
@ -3501,19 +3521,19 @@ msgctxt "filename"
msgid "bookings.ods" msgid "bookings.ods"
msgstr "reservations.ods" msgstr "reservations.ods"
#: pkg/booking/admin.go:591 #: pkg/booking/admin.go:473
msgid "Country can not be empty to validate the postcode." msgid "Country can not be empty to validate the postcode."
msgstr "Le pays ne peut pas être vide pour valider le code postal." msgstr "Le pays ne peut pas être vide pour valider le code postal."
#: pkg/booking/admin.go:601 #: pkg/booking/admin.go:483
msgid "Country can not be empty to validate the phone." msgid "Country can not be empty to validate the phone."
msgstr "Le pays ne peut pas être vide pour valider le téléphone." msgstr "Le pays ne peut pas être vide pour valider le téléphone."
#: pkg/booking/admin.go:608 #: pkg/booking/admin.go:490
msgid "You must select at least one accommodation." msgid "You must select at least one accommodation."
msgstr "Vous devez sélectionner au moins un hébergement." msgstr "Vous devez sélectionner au moins un hébergement."
#: pkg/booking/admin.go:614 #: pkg/booking/admin.go:496
msgid "The selected accommodations have no available openings in the requested dates." msgid "The selected accommodations have no available openings in the requested dates."
msgstr "Les hébergements sélectionnés nont pas de disponibilités aux dates demandées." msgstr "Les hébergements sélectionnés nont pas de disponibilités aux dates demandées."
@ -3630,6 +3650,9 @@ msgstr "%s doit être tout au plus %d."
msgid "It is mandatory to agree to the reservation conditions." msgid "It is mandatory to agree to the reservation conditions."
msgstr "Il est obligatoire daccepter les conditions de réservation." msgstr "Il est obligatoire daccepter les conditions de réservation."
#~ msgid "Total"
#~ msgstr "Totale"
#~ msgid "Select a customer" #~ msgid "Select a customer"
#~ msgstr "Choisissez un client" #~ msgstr "Choisissez un client"

View File

@ -0,0 +1,9 @@
-- Revert camper:campsite_type__operating_dates from pg
begin;
alter table camper.campsite_type
drop column if exists operating_dates
;
commit;

View File

@ -333,3 +333,4 @@ booking_invoice [roles schema_camper booking invoice] 2024-04-28T19:45:05Z jordi
marshal_payment [roles schema_camper payment payment_customer payment_option payment__acsi_card payment_customer__-acsi_card] 2024-04-29T17:11:59Z jordi fita mas <jordi@tandem.blog> # Add function to marshal a payment marshal_payment [roles schema_camper payment payment_customer payment_option payment__acsi_card payment_customer__-acsi_card] 2024-04-29T17:11:59Z jordi fita mas <jordi@tandem.blog> # Add function to marshal a payment
unmarshal_booking [roles schema_camper booking booking_option extension_pg_libphonenumber] 2024-04-29T17:20:38Z jordi fita mas <jordi@tandem.blog> # Add function to unmarshal a booking unmarshal_booking [roles schema_camper booking booking_option extension_pg_libphonenumber] 2024-04-29T17:20:38Z jordi fita mas <jordi@tandem.blog> # Add function to unmarshal a booking
cancel_booking [roles schema_camper booking booking_campsite] 2024-05-03T14:27:31Z jordi fita mas <jordi@tandem.blog> # Add function to cancel a booking cancel_booking [roles schema_camper booking booking_campsite] 2024-05-03T14:27:31Z jordi fita mas <jordi@tandem.blog> # Add function to cancel a booking
campsite_type__operating_dates [campsite_type] 2024-07-15T21:27:19Z jordi fita mas <jordi@tandem.blog> # Add operating_dates field to campsite_type

38
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,38 @@
qt_add_executable(${PROJECT_NAME}
main.cpp
)
qt_add_qml_module(${PROJECT_NAME}
URI Camper
VERSION 1.0
DEPENDENCIES QtCore
SOURCES
database.cpp database.h
QML_FILES
ErrorNotification.qml
Main.qml
SelectableLabel.qml
)
set_target_properties(${PROJECT_NAME} PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER ws.tandem.${PROJECT_NAME}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Concurrent
Qt6::Quick
Qt6::QuickControls2
Qt6::Sql
)
include(GNUInstallDirs)
install(TARGETS ${PROJECT_NAME}
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

117
src/ErrorNotification.qml Normal file
View File

@ -0,0 +1,117 @@
import QtQuick
import QtQuick.Controls
Control {
id: control
property alias text: label.text
function show(errorMessage: string) {
control.text = errorMessage;
control.visible = true;
hideTimer.start();
}
Accessible.ignored: !visible
Accessible.role: Accessible.AlertMessage
implicitHeight: visible ? (contentLayout.implicitHeight + topPadding + bottomPadding) : 0
opacity: visible ? 1 : 0
padding: 4
visible: false
background: Rectangle {
id: borderRect
border.color: "#da4453"
color: "#ebced2"
radius: 5
}
contentItem: Item {
id: contentLayout
Accessible.ignored: true
implicitHeight: Math.max(label.implicitHeight, closeButton.implicitHeight)
Behavior on opacity {
enabled: control.visible
NumberAnimation {
duration: 200
}
}
SelectableLabel {
id: label
Accessible.ignored: !control.visible
anchors {
left: parent.left
leftMargin: 4
right: closeButton.left
rightMargin: 4
top: parent.top
}
}
ToolButton {
id: closeButton
Accessible.ignored: !control.visible
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
display: ToolButton.IconOnly
height: implicitHeight
icon.name: "dialog-close"
text: qsTr("Close")
onClicked: function () {
control.visible = false;
}
}
}
Behavior on implicitHeight {
enabled: !control.visible
NumberAnimation {
duration: 200
}
}
Behavior on opacity {
enabled: !control.visible
NumberAnimation {
duration: 200
}
}
onImplicitHeightChanged: function () {
height = implicitHeight;
}
onOpacityChanged: function () {
if (opacity === 0) {
contentLayout.opacity = 0;
} else if (opacity === 1) {
contentLayout.opacity = 1;
}
}
anchors {
bottom: parent.bottom
bottomMargin: 8
left: parent.left
margins: 18 * 4
right: parent.right
}
Timer {
id: hideTimer
interval: 10000
repeat: false
onTriggered: function () {
control.visible = false;
}
}
}

80
src/Main.qml Normal file
View File

@ -0,0 +1,80 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Camper
ApplicationWindow {
height: 480
title: qsTr("Camper")
visible: true
width: 640
ColumnLayout {
Label {
text: qsTr("&User:")
}
TextField {
id: user
focus: true
validator: RegularExpressionValidator {
regularExpression: /[^s].*/
}
onAccepted: function () {
loginAction.trigger();
}
}
Label {
text: qsTr("&Password:")
}
TextField {
id: password
echoMode: TextInput.Password
onAccepted: function () {
loginAction.trigger();
}
}
Button {
action: loginAction
}
}
ErrorNotification {
id: errorNotification
anchors {
bottom: parent.bottom
bottomMargin: 8
left: parent.left
margins: 18 * 4
right: parent.right
}
}
Action {
id: loginAction
enabled: user.acceptableInput
text: "&Login"
onTriggered: function () {
Database.open(user.text, password.text);
}
}
Connections {
function onErrorOcurred(errorMessage) {
errorNotification.show(errorMessage);
}
target: Database
}
}

21
src/SelectableLabel.qml Normal file
View File

@ -0,0 +1,21 @@
import QtQuick
import QtQuick.Controls
Control {
id: control
property alias text: textArea.text
contentItem: TextArea {
id: textArea
padding: 0
readOnly: true
selectByMouse: true
wrapMode: Text.WordWrap
HoverHandler {
cursorShape: Qt.IBeamCursor
}
}
}

27
src/database.cpp Normal file
View File

@ -0,0 +1,27 @@
#include "database.h"
#include <QSqlDatabase>
#include <QSqlError>
#include <QtConcurrent>
Database::Database(QObject *parent)
: QObject{parent}
, m_pool{}
{
m_pool.setMaxThreadCount(1);
m_pool.setExpiryTimeout(-1);
}
QFuture<void> Database::open(const QString &user, const QString &password)
{
return QtConcurrent::run(&m_pool, [this, user, password]() {
QString connectionName("main");
QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL", connectionName);
db.setConnectOptions("service=camper; options=-csearch_path=camper,public");
if (!db.open(user, password)) {
const QString errorMessage(db.lastError().text());
db = QSqlDatabase(); // Otherwise removeDatabase complains is still being used.
QSqlDatabase::removeDatabase(connectionName);
emit errorOcurred(errorMessage);
}
});
}

27
src/database.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef DATABASE_H
#define DATABASE_H
#include <QFuture>
#include <QObject>
#include <QThreadPool>
#include <QtQmlIntegration>
class Database : public QObject
{
Q_OBJECT
QML_SINGLETON
QML_ELEMENT
public:
explicit Database(QObject *parent = nullptr);
Q_INVOKABLE QFuture<void> open(const QString &user, const QString &password);
signals:
void errorOcurred(const QString &errorMessage);
private:
QThreadPool m_pool;
};
#endif // DATABASE_H

21
src/main.cpp Normal file
View File

@ -0,0 +1,21 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setApplicationName("Camper");
QCoreApplication::setOrganizationName("Tandem");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("Camper", "Main");
return app.exec();
}

View File

@ -5,7 +5,7 @@ reset client_min_messages;
begin; begin;
select plan(108); select plan(113);
set search_path to camper, public; set search_path to camper, public;
@ -118,6 +118,12 @@ select col_not_null('campsite_type', 'active');
select col_has_default('campsite_type', 'active'); select col_has_default('campsite_type', 'active');
select col_default_is('campsite_type', 'active', 'true'); select col_default_is('campsite_type', 'active', 'true');
select has_column('campsite_type', 'operating_dates');
select col_type_is('campsite_type', 'operating_dates', 'daterange');
select col_not_null('campsite_type', 'operating_dates');
select col_has_default('campsite_type', 'operating_dates');
select col_default_is('campsite_type', 'operating_dates', 'empty');
select has_column('campsite_type', 'position'); select has_column('campsite_type', 'position');
select col_type_is('campsite_type', 'position', 'integer'); select col_type_is('campsite_type', 'position', 'integer');
select col_not_null('campsite_type', 'position'); select col_not_null('campsite_type', 'position');

View File

@ -0,0 +1,10 @@
-- Verify camper:campsite_type__operating_dates on pg
begin;
select operating_dates
from camper.campsite_type
where false
;
rollback;

View File

@ -256,7 +256,7 @@
<label class="colspan" data-hx-get="{{ $.URL }}" data-hx-trigger="change"> <label class="colspan" data-hx-get="{{ $.URL }}" data-hx-trigger="change">
<input type="checkbox" name="{{ .Name }}" {{ if .Checked}}checked{{ end }} <input type="checkbox" name="{{ .Name }}" {{ if .Checked}}checked{{ end }}
{{ template "error-attrs" . }} {{ template "error-attrs" . }}
> {{( pgettext "ACSI card? (optional)" "input" )}}<br> > {{( pgettext "ACSI / ANWB card? (optional)" "input" )}}<br>
{{ template "error-message" . }} {{ template "error-message" . }}
</label> </label>
{{- end }} {{- end }}

View File

@ -7,22 +7,4 @@
<td class="booking-status">{{ .StatusLabel }}</td> <td class="booking-status">{{ .StatusLabel }}</td>
</tr> </tr>
{{- end }} {{- end }}
{{ if .Filters.Cursor.Val }} {{ template "pagination" .Filters.Cursor | colspan 5 }}
<tr>
<td colspan="5">
{{ with .Filters -}}
<form data-hx-get="/admin/bookings" data-hx-target="closest tr" data-hx-swap="outerHTML">
{{ with .HolderName -}}<input type="hidden" name="{{ .Name }}"
value="{{ .Val }}">{{- end }}
{{ with .BookingStatus -}}<input type="hidden" name="{{ .Name }}"
value="{{ .String }}">{{- end }}
{{ with .FromDate -}}<input type="hidden" name="{{ .Name }}"
value="{{ .Val }}">{{- end }}
{{ with .ToDate -}}<input type="hidden" name="{{ .Name }}" value="{{ .Val }}">{{- end }}
{{ with .Cursor -}}<input type="hidden" name="{{ .Name }}" value="{{ .Val }}">{{- end }}
<button type="submit">{{( pgettext "Load more" "action" )}}</button>
</form>
{{- end }}
<td>
</tr>
{{- end }}

View File

@ -5,15 +5,4 @@
<td>{{ .Phone }}</td> <td>{{ .Phone }}</td>
</tr> </tr>
{{- end }} {{- end }}
{{ if .Filters.Cursor.Val }} {{ template "pagination" .Filters.Cursor | colspan 3 }}
<tr>
<td colspan="3">
{{ with .Filters -}}
<form method="get" data-hx-push-url="false" data-hx-boost="true" data-hx-target="closest tr" data-hx-swap="outerHTML">
{{ with .Cursor -}}<input type="hidden" name="{{ .Name }}" value="{{ .Val }}">{{- end }}
<button type="submit">{{( pgettext "Load more" "action" )}}</button>
</form>
{{- end }}
<td>
</tr>
{{- end }}

View File

@ -82,3 +82,17 @@
@click="document.body.classList.toggle('filters-visible')" @click="document.body.classList.toggle('filters-visible')"
type="button">{{(pgettext "Filters" "action")}}</button> type="button">{{(pgettext "Filters" "action")}}</button>
{{- end }} {{- end }}
{{ define "pagination" -}}
{{ if .Val }}
<tr>
<td colspan="{{ .Colspan }}">
<form method="get" data-hx-push-url="false" data-hx-boost="true" data-hx-target="closest tr"
data-hx-swap="outerHTML">
<input type="hidden" name="{{ .Name }}" value="{{ .Val }}">
<button type="submit">{{( pgettext "Load more" "action" )}}</button>
</form>
<td>
</tr>
{{- end }}
{{- end }}

View File

@ -96,73 +96,24 @@
</form> </form>
<a href="/admin/invoices/new">{{( pgettext "Add invoice" "action" )}}</a> <a href="/admin/invoices/new">{{( pgettext "Add invoice" "action" )}}</a>
<h2>{{ template "title" . }}</h2> <h2>{{ template "title" . }}</h2>
<table id="invoice-list"> {{ if .Invoices }}
<thead> <table id="invoice-list">
<tr> <thead>
<th>{{( pgettext "All" "invoice" )}}</th>
<th>{{( pgettext "Date" "title" )}}</th>
<th>{{( pgettext "Invoice Num." "title" )}}</th>
<th>{{( pgettext "Customer" "title" )}}</th>
<th>{{( pgettext "Status" "title" )}}</th>
<th>{{( pgettext "Download" "title" )}}</th>
<th class="numeric">{{( pgettext "Amount" "title" )}}</th>
</tr>
</thead>
<tbody>
{{ with .Invoices }}
{{- range $invoice := . }}
<tr>
{{ $title := .Number | printf (pgettext "Select invoice %v" "action") }}
<td><input type="checkbox" form="batch-form"
name="invoice" value="{{ .Slug }}"
aria-label="{{ $title }}"
title="{{ $title }}"/></td>
<td>{{ .Date|formatDate }}</td>
<td><a href="/admin/invoices/{{ .Slug }}">{{ .Number }}</a></td>
<td>{{ .CustomerName }}</td>
<td class="invoice-status-{{ .Status }}">
<details class="invoice-status menu">
<summary >{{ .StatusLabel }}</summary>
<form data-hx-put="/admin/invoices/{{ .Slug }}">
{{ CSRFInput }}
<input type="hidden" name="quick" value="status">
<ul role="menu">
{{- range $status, $name := $.InvoiceStatuses }}
{{- if ne $status $invoice.Status }}
<li role="presentation">
<button role="menuitem" type="submit"
name="invoice_status" value="{{ $status }}"
class="invoice-status-{{ $status }}"
>{{ $name }}</button>
</li>
{{- end }}
{{- end }}
</ul>
</form>
</details>
</td>
{{- $title = .Number | printf (pgettext "Download invoice %s" "action") -}}
<td class="invoice-download"><a href="/admin/invoices/{{ .Slug }}.pdf"
download="{{ .Number}}-{{ .CustomerName | slugify }}.pdf"
title="{{( pgettext "Download invoice" "action" )}}"
aria-label="{{ $title }}">⤓</a></td>
<td class="numeric">{{ .Total|formatPrice }}</td>
</tr>
{{- end }}
{{ else }}
<tr> <tr>
<td colspan="9">{{( gettext "No invoices added yet." )}}</td> <th>{{( pgettext "All" "invoice" )}}</th>
<th>{{( pgettext "Date" "title" )}}</th>
<th>{{( pgettext "Invoice Num." "title" )}}</th>
<th>{{( pgettext "Customer" "title" )}}</th>
<th>{{( pgettext "Status" "title" )}}</th>
<th>{{( pgettext "Download" "title" )}}</th>
<th class="numeric">{{( pgettext "Amount" "title" )}}</th>
</tr> </tr>
{{ end }} </thead>
</tbody> <tbody>
{{ if .Invoices }} {{ template "results.gohtml" . }}
<tfoot> </tbody>
<tr> </table>
<th scope="row" colspan="6">{{( gettext "Total" )}}</th> {{- else -}}
<td class="numeric">{{ .TotalAmount|formatPrice }}</td> <p>{{( gettext "No invoices found." )}}</p>
<td colspan="2"></td> {{ end }}
</tr>
</tfoot>
{{ end }}
</table>
{{- end }} {{- end }}

View File

@ -0,0 +1,40 @@
{{- range $invoice := .Invoices }}
<tr>
{{ $title := .Number | printf (pgettext "Select invoice %v" "action") }}
<td><input type="checkbox" form="batch-form"
name="invoice" value="{{ .Slug }}"
aria-label="{{ $title }}"
title="{{ $title }}"/></td>
<td>{{ .Date|formatDate }}</td>
<td><a href="/admin/invoices/{{ .Slug }}">{{ .Number }}</a></td>
<td>{{ .CustomerName }}</td>
<td class="invoice-status-{{ .Status }}">
<details class="invoice-status menu">
<summary>{{ .StatusLabel }}</summary>
<form data-hx-put="/admin/invoices/{{ .Slug }}">
{{ CSRFInput }}
<input type="hidden" name="quick" value="status">
<ul role="menu">
{{- range $status, $name := $.InvoiceStatuses }}
{{- if ne $status $invoice.Status }}
<li role="presentation">
<button role="menuitem" type="submit"
name="invoice_status" value="{{ $status }}"
class="invoice-status-{{ $status }}"
>{{ $name }}</button>
</li>
{{- end }}
{{- end }}
</ul>
</form>
</details>
</td>
{{- $title = .Number | printf (pgettext "Download invoice %s" "action") -}}
<td class="invoice-download"><a href="/admin/invoices/{{ .Slug }}.pdf"
download="{{ .Number}}-{{ .CustomerName | slugify }}.pdf"
title="{{( pgettext "Download invoice" "action" )}}"
aria-label="{{ $title }}">⤓</a></td>
<td class="numeric">{{ .Total|formatPrice }}</td>
</tr>
{{- end }}
{{ template "pagination" .Filters.Cursor | colspan 7 }}

View File

@ -79,7 +79,7 @@
<td>{{ .ZonePreferences }}</td> <td>{{ .ZonePreferences }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{{( pgettext "ACSI card?" "input" )}}</th> <th scope="row">{{( pgettext "ACSI / ANWB card?" "input" )}}</th>
<td>{{if .ACSICard}}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td> <td>{{if .ACSICard}}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td>
</tr> </tr>
<tr> <tr>

View File

@ -12,6 +12,58 @@
{{ define "content" -}} {{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/payment.paymentIndex*/ -}} {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/payment.paymentIndex*/ -}}
<a href="/admin/payments/settings">{{( pgettext "Payment Settings" "title" )}}</a> <a href="/admin/payments/settings">{{( pgettext "Payment Settings" "title" )}}</a>
{{ template "filters-toggle" }}
<form class="filters" method="GET" action="/admin/payments"
data-hx-target="main" data-hx-boost="true" data-hx-trigger="change,search,submit"
aria-labelledby="filters-toggle"
>
{{ with .Filters }}
<fieldset>
{{ with .PaymentStatus -}}
<label>
{{( pgettext "Payment status" "input" )}}<br>
<select name="{{ .Name }}"
{{ template "error-attrs" . }}
>
<option value="">{{( gettext "All statuses" )}}</option>
{{ template "list-options" . }}
</select><br>
{{ template "error-message" . }}
</label>
{{- end }}
{{ with .FromDate -}}
<label>
{{( pgettext "From date" "input" )}}<br>
<input type="date"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
{{ template "error-message" . }}
</label>
{{- end }}
{{ with .ToDate -}}
<label>
{{( pgettext "To date" "input" )}}<br>
<input type="date"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
{{ template "error-message" . }}
</label>
{{- end }}
{{ with .Reference -}}
<label>
{{( pgettext "Reference" "input" )}}<br>
<input type="search"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
{{ template "error-message" . }}
</label>
{{- end }}
</fieldset>
{{ end }}
{{ if .Filters.HasValue }}
<a href="/admin/payments" class="button">{{( pgettext "Reset" "action" )}}</a>
{{ end }}
</form>
<h2>{{ template "title" . }}</h2> <h2>{{ template "title" . }}</h2>
{{ if .Payments -}} {{ if .Payments -}}
<table> <table>
@ -25,15 +77,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{ range .Payments -}} {{ template "results.gohtml" . }}
<tr class="payment-{{ .Status }}">
<td>{{ .CreatedAt | formatDate }}</td>
<td><a href="{{ .URL }}">{{ .Reference }}</a></td>
<td class="payment-status">{{ .StatusLabel }}</td>
<td class="numeric">{{ .DownPayment | formatPrice }}</td>
<td class="numeric">{{ .Total | formatPrice }}</td>
</tr>
{{- end }}
</tbody> </tbody>
</table> </table>
{{ else -}} {{ else -}}

View File

@ -0,0 +1,10 @@
{{ range .Payments -}}
<tr class="payment-{{ .Status }}">
<td>{{ .CreatedAt | formatDate }}</td>
<td><a href="{{ .URL }}">{{ .Reference }}</a></td>
<td class="payment-status">{{ .StatusLabel }}</td>
<td class="numeric">{{ .DownPayment | formatPrice }}</td>
<td class="numeric">{{ .Total | formatPrice }}</td>
</tr>
{{- end }}
{{ template "pagination" .Filters.Cursor | colspan 5 }}

View File

@ -12,25 +12,53 @@
{{ define "content" -}} {{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/user.loginAttemptIndex*/ -}} {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/user.loginAttemptIndex*/ -}}
{{ template "filters-toggle" }}
<form class="filters" method="GET" action="/admin/users/login-attempts"
data-hx-target="main" data-hx-boost="true" data-hx-trigger="change,search,submit"
aria-labelledby="filters-toggle"
>
{{ with .Filters }}
<fieldset>
{{ with .FromDate -}}
<label>
{{( pgettext "From date" "input" )}}<br>
<input type="date"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
{{ template "error-message" . }}
</label>
{{- end }}
{{ with .ToDate -}}
<label>
{{( pgettext "To date" "input" )}}<br>
<input type="date"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
{{ template "error-message" . }}
</label>
{{- end }}
</fieldset>
{{ end }}
{{ if .Filters.HasValue }}
<a href="/admin/users/login-attempts" class="button">{{( pgettext "Reset" "action" )}}</a>
{{ end }}
</form>
<h2>{{( pgettext "Login Attempts" "title" )}}</h2> <h2>{{( pgettext "Login Attempts" "title" )}}</h2>
<table> {{ if .Attempts -}}
<thead> <table>
<tr> <thead>
<th scope="col">{{( pgettext "Date" "header" )}}</th>
<th scope="col">{{( pgettext "Email" "header" )}}</th>
<th scope="col">{{( pgettext "IP Address" "header" )}}</th>
<th scope="col">{{( pgettext "Success" "header" )}}</th>
</tr>
</thead>
<tbody>
{{ range . -}}
<tr> <tr>
<td>{{ .Date }}</td> <th scope="col">{{( pgettext "Date" "header" )}}</th>
<td>{{ .UserName }}</td> <th scope="col">{{( pgettext "Email" "header" )}}</th>
<td>{{ .IPAddress }}</td> <th scope="col">{{( pgettext "IP Address" "header" )}}</th>
<td>{{ if .Success }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td> <th scope="col">{{( pgettext "Success" "header" )}}</th>
</tr> </tr>
{{- end }} </thead>
</tbody> <tbody>
</table> {{ template "results.gohtml" . }}
</tbody>
</table>
{{- else -}}
<p>{{( gettext "No logging attempts found." )}}</p>
{{- end }}
{{- end }} {{- end }}

View File

@ -0,0 +1,9 @@
{{ range .Attempts -}}
<tr>
<td>{{ .Date }}</td>
<td>{{ .UserName }}</td>
<td>{{ .IPAddress }}</td>
<td>{{ if .Success }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td>
</tr>
{{- end }}
{{ template "pagination" .Filters.Cursor | colspan 4 }}

View File

@ -15,7 +15,7 @@
* {{( pgettext "Accommodation" "title" )}}: {{ .CampsiteType }} * {{( pgettext "Accommodation" "title" )}}: {{ .CampsiteType }}
* {{( pgettext "Area preferences" "header" )}}: {{ .ZonePreferences }} * {{( pgettext "Area preferences" "header" )}}: {{ .ZonePreferences }}
* {{( pgettext "ACSI card?" "input" )}}: {{if .ACSICard}}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }} * {{( pgettext "ACSI / ANWB card?" "input" )}}: {{if .ACSICard}}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}
* {{( pgettext "Arrival date" "input" )}}: {{ .ArrivalDate.Format "02/01/2006" }} * {{( pgettext "Arrival date" "input" )}}: {{ .ArrivalDate.Format "02/01/2006" }}
* {{( pgettext "Departure date" "input" )}}: {{ .DepartureDate.Format "02/01/2006" }} * {{( pgettext "Departure date" "input" )}}: {{ .DepartureDate.Format "02/01/2006" }}
* {{( pgettext "Nights" "cart" )}}: {{ .NumNights }} * {{( pgettext "Nights" "cart" )}}: {{ .NumNights }}

View File

@ -244,7 +244,7 @@
> >
<input type="checkbox" name="{{ .Name }}" {{ if .Checked}}checked{{ end }} <input type="checkbox" name="{{ .Name }}" {{ if .Checked}}checked{{ end }}
{{ template "error-attrs" . }} {{ template "error-attrs" . }}
> {{( pgettext "ACSI card? (optional)" "input" )}}<br> > {{( pgettext "ACSI / ANWB card? (optional)" "input" )}}<br>
{{ template "error-message" . }} {{ template "error-message" . }}
</label> </label>
{{- end }} {{- end }}