Change draft_payment return type to row of payment
This way i can use the function in the from clause of the query that i already had to do to get the totals formatted with to_price. In this case, i believe it is better to leave out Go’s function because it would force me to perform two queries. Instead of binding a nullable string pointer with the payment’s slug, i wanted to use pgtype’s zeronull.Text type, but it can not work in this case because it encodes the value as a text, while the parameters is uuid. I can not use zero.UUID, because it is a [16]byte array, while i have it in a string. Thus, had to create my own ZeroNullUUID type that use a string as a base but encodes it as a UUID.
This commit is contained in:
parent
77a3f78176
commit
990a614897
|
@ -17,10 +17,10 @@ set search_path to camper, public;
|
||||||
|
|
||||||
create type option_units as (option_id integer, units integer);
|
create type option_units as (option_id integer, units integer);
|
||||||
|
|
||||||
create or replace function draft_payment(payment_slug uuid, arrival_date date, departure_date date, campsite_type_slug uuid, num_adults integer, num_teenagers integer, num_children integer, num_dogs integer, zone_preferences text, options option_units[]) returns uuid as
|
create or replace function draft_payment(payment_slug uuid, arrival_date date, departure_date date, campsite_type_slug uuid, num_adults integer, num_teenagers integer, num_children integer, num_dogs integer, zone_preferences text, options option_units[]) returns payment as
|
||||||
$$
|
$$
|
||||||
declare
|
declare
|
||||||
pid integer;
|
p payment;
|
||||||
begin
|
begin
|
||||||
insert into payment (
|
insert into payment (
|
||||||
slug
|
slug
|
||||||
|
@ -86,14 +86,14 @@ begin
|
||||||
, total = excluded.total
|
, total = excluded.total
|
||||||
, zone_preferences = excluded.zone_preferences
|
, zone_preferences = excluded.zone_preferences
|
||||||
, updated_at = current_timestamp
|
, updated_at = current_timestamp
|
||||||
returning payment_id, payment.slug
|
returning *
|
||||||
into pid, payment_slug
|
into p
|
||||||
;
|
;
|
||||||
|
|
||||||
if array_length(coalesce(options, array[]::option_units[]), 1) > 0 then
|
if array_length(coalesce(options, array[]::option_units[]), 1) > 0 then
|
||||||
delete
|
delete
|
||||||
from payment_option
|
from payment_option
|
||||||
where payment_id = pid
|
where payment_id = p.payment_id
|
||||||
and campsite_type_option_id not in (
|
and campsite_type_option_id not in (
|
||||||
select campsite_type_option_id
|
select campsite_type_option_id
|
||||||
from unnest(options) as option(campsite_type_option_id, units)
|
from unnest(options) as option(campsite_type_option_id, units)
|
||||||
|
@ -105,7 +105,7 @@ begin
|
||||||
, units
|
, units
|
||||||
, subtotal
|
, subtotal
|
||||||
)
|
)
|
||||||
select pid
|
select p.payment_id
|
||||||
, campsite_type_option_id
|
, campsite_type_option_id
|
||||||
, units
|
, units
|
||||||
, case when per_night then sum(cost * units)::integer else max(cost * units)::integer end
|
, case when per_night then sum(cost * units)::integer else max(cost * units)::integer end
|
||||||
|
@ -125,26 +125,26 @@ begin
|
||||||
with option as (
|
with option as (
|
||||||
select sum(subtotal)::integer as subtotal
|
select sum(subtotal)::integer as subtotal
|
||||||
from payment_option
|
from payment_option
|
||||||
where payment_id = pid
|
where payment_id = p.payment_id
|
||||||
)
|
)
|
||||||
update payment
|
update payment
|
||||||
set total = subtotal_nights + subtotal_adults + subtotal_teenagers + subtotal_children + subtotal_dogs + subtotal_tourist_tax + coalesce(option.subtotal, 0)
|
set total = subtotal_nights + subtotal_adults + subtotal_teenagers + subtotal_children + subtotal_dogs + subtotal_tourist_tax + coalesce(option.subtotal, 0)
|
||||||
from option
|
from option
|
||||||
where payment_id = pid
|
where payment_id = p.payment_id
|
||||||
;
|
;
|
||||||
else
|
else
|
||||||
delete
|
delete
|
||||||
from payment_option
|
from payment_option
|
||||||
where payment_id = pid;
|
where payment_id = p.payment_id;
|
||||||
|
|
||||||
update payment
|
update payment
|
||||||
set total = subtotal_nights + subtotal_adults + subtotal_teenagers + subtotal_children + subtotal_dogs + subtotal_tourist_tax
|
set total = subtotal_nights + subtotal_adults + subtotal_teenagers + subtotal_children + subtotal_dogs + subtotal_tourist_tax
|
||||||
where payment_id = pid
|
where payment_id = p.payment_id
|
||||||
;
|
;
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
|
|
||||||
return payment_slug;
|
return p;
|
||||||
end;
|
end;
|
||||||
$$
|
$$
|
||||||
language plpgsql
|
language plpgsql
|
||||||
|
|
|
@ -82,26 +82,9 @@ func newBookingCart(ctx context.Context, conn *database.Conn, f *bookingForm, ca
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
paymentSlug, err := conn.DraftPayment(
|
|
||||||
ctx,
|
|
||||||
f.PaymentSlug.Val,
|
|
||||||
arrivalDate,
|
|
||||||
departureDate,
|
|
||||||
campsiteType,
|
|
||||||
numAdults,
|
|
||||||
numTeenagers,
|
|
||||||
numChildren,
|
|
||||||
numDogs,
|
|
||||||
zonePreferences,
|
|
||||||
optionUnits,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f.PaymentSlug.Val = paymentSlug
|
|
||||||
|
|
||||||
row := conn.QueryRow(ctx, `
|
row := conn.QueryRow(ctx, `
|
||||||
select payment_id
|
select payment.slug
|
||||||
|
, payment_id
|
||||||
, departure_date - arrival_date
|
, departure_date - arrival_date
|
||||||
, to_price(subtotal_nights, decimal_digits)
|
, to_price(subtotal_nights, decimal_digits)
|
||||||
, to_price(subtotal_adults, decimal_digits)
|
, to_price(subtotal_adults, decimal_digits)
|
||||||
|
@ -110,12 +93,21 @@ func newBookingCart(ctx context.Context, conn *database.Conn, f *bookingForm, ca
|
||||||
, to_price(subtotal_dogs, decimal_digits)
|
, to_price(subtotal_dogs, decimal_digits)
|
||||||
, to_price(subtotal_tourist_tax, decimal_digits)
|
, to_price(subtotal_tourist_tax, decimal_digits)
|
||||||
, to_price(total, decimal_digits)
|
, to_price(total, decimal_digits)
|
||||||
from payment
|
from draft_payment($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) as payment
|
||||||
join company using (company_id)
|
join company using (company_id)
|
||||||
join currency using (currency_code)
|
join currency using (currency_code)
|
||||||
where payment.slug = $1
|
`,
|
||||||
`, paymentSlug)
|
database.ZeroNullUUID(f.PaymentSlug.Val),
|
||||||
|
arrivalDate,
|
||||||
|
departureDate,
|
||||||
|
campsiteType,
|
||||||
|
numAdults,
|
||||||
|
numTeenagers,
|
||||||
|
numChildren,
|
||||||
|
numDogs,
|
||||||
|
zonePreferences,
|
||||||
|
database.OptionUnitsArray(optionUnits),
|
||||||
|
)
|
||||||
var paymentID int
|
var paymentID int
|
||||||
var numNights int
|
var numNights int
|
||||||
var nights string
|
var nights string
|
||||||
|
@ -125,7 +117,7 @@ func newBookingCart(ctx context.Context, conn *database.Conn, f *bookingForm, ca
|
||||||
var dogs string
|
var dogs string
|
||||||
var touristTax string
|
var touristTax string
|
||||||
var total string
|
var total string
|
||||||
if err = row.Scan(&paymentID, &numNights, &nights, &adults, &teenagers, &children, &dogs, &touristTax, &total); err != nil {
|
if err = row.Scan(&f.PaymentSlug.Val, &paymentID, &numNights, &nights, &adults, &teenagers, &children, &dogs, &touristTax, &total); err != nil {
|
||||||
if database.ErrorIsNotFound(err) {
|
if database.ErrorIsNotFound(err) {
|
||||||
return cart, nil
|
return cart, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@ package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -350,14 +348,6 @@ func (tx *Tx) TranslateHome(ctx context.Context, companyID int, langTag language
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) DraftPayment(ctx context.Context, paymentSlug string, arrivalDate time.Time, departureDate time.Time, campsiteTypeSlug string, numAdults int, numTeenagers int, numChildren int, numDogs int, zonePreferences string, options OptionUnitsArray) (string, error) {
|
|
||||||
var paymentSlugParam *string
|
|
||||||
if paymentSlug != "" {
|
|
||||||
paymentSlugParam = &paymentSlug
|
|
||||||
}
|
|
||||||
return c.GetText(ctx, "select draft_payment($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", paymentSlugParam, arrivalDate, departureDate, campsiteTypeSlug, numAdults, numTeenagers, numChildren, numDogs, zonePreferences, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReadyPayment(ctx context.Context, paymentSlug string, customerName string, customerAddress string, customerPostCode string, customerCity string, customerCountryCode string, customerEmail string, customerPhone string, customerLangTag language.Tag, acsiCard bool) (int, error) {
|
func (c *Conn) ReadyPayment(ctx context.Context, paymentSlug string, customerName string, customerAddress string, customerPostCode string, customerCity string, customerCountryCode string, customerEmail string, customerPhone string, customerLangTag language.Tag, acsiCard bool) (int, error) {
|
||||||
return c.GetInt(ctx, "select ready_payment($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", paymentSlug, customerName, customerAddress, customerPostCode, customerCity, customerCountryCode, customerEmail, customerPhone, customerLangTag, acsiCard)
|
return c.GetInt(ctx, "select ready_payment($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", paymentSlug, customerName, customerAddress, customerPostCode, customerCity, customerCountryCode, customerEmail, customerPhone, customerLangTag, acsiCard)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jackc/pgtype"
|
||||||
|
|
||||||
|
"dev.tandem.ws/tandem/camper/pkg/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ZeroNullUUID string
|
||||||
|
|
||||||
|
func (src ZeroNullUUID) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) {
|
||||||
|
if src == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := uuid.Parse(string(src))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nullable := pgtype.UUID{
|
||||||
|
Bytes: bytes,
|
||||||
|
Status: pgtype.Present,
|
||||||
|
}
|
||||||
|
return nullable.EncodeBinary(ci, buf)
|
||||||
|
}
|
|
@ -5,7 +5,12 @@
|
||||||
|
|
||||||
package uuid
|
package uuid
|
||||||
|
|
||||||
import "dev.tandem.ws/tandem/camper/pkg/hex"
|
import (
|
||||||
|
gohex "encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"dev.tandem.ws/tandem/camper/pkg/hex"
|
||||||
|
)
|
||||||
|
|
||||||
func Valid(s string) bool {
|
func Valid(s string) bool {
|
||||||
if len(s) != 36 {
|
if len(s) != 36 {
|
||||||
|
@ -27,3 +32,23 @@ func Valid(s string) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Parse(src string) (dst [16]byte, err error) {
|
||||||
|
switch len(src) {
|
||||||
|
case 36:
|
||||||
|
src = src[0:8] + src[9:13] + src[14:18] + src[19:23] + src[24:]
|
||||||
|
case 32:
|
||||||
|
// dashes already stripped, assume valid
|
||||||
|
default:
|
||||||
|
// assume invalid.
|
||||||
|
return dst, fmt.Errorf("cannot parse UUID %v", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := gohex.DecodeString(src)
|
||||||
|
if err != nil {
|
||||||
|
return dst, err
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(dst[:], buf)
|
||||||
|
return dst, err
|
||||||
|
}
|
||||||
|
|
|
@ -13,15 +13,16 @@ import (
|
||||||
type test struct {
|
type test struct {
|
||||||
in string
|
in string
|
||||||
isUuid bool
|
isUuid bool
|
||||||
|
bytes [16]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
var tests = []test{
|
var tests = []test{
|
||||||
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", true},
|
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", true, [16]byte{0xf4, 0x7a, 0xc1, 0x0b, 0x58, 0xcc, 0x03, 0x72, 0x85, 0x67, 0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79}},
|
||||||
{"2bc1be74-169d-4300-a239-49a1196a045d", true},
|
{"2bc1be74-169d-4300-a239-49a1196a045d", true, [16]byte{0x2b, 0xc1, 0xbe, 0x74, 0x16, 0x9d, 0x43, 0x00, 0xa2, 0x39, 0x49, 0xa1, 0x19, 0x6a, 0x04, 0x5d}},
|
||||||
{"12bc1be74-169d-4300-a239-49a1196a045d", false},
|
{"12bc1be74-169d-4300-a239-49a1196a045d", false, [16]byte{}},
|
||||||
{"2bc1be74-169d-4300-a239-49a1196a045", false},
|
{"2bc1be74-169d-4300-a239-49a1196a045", false, [16]byte{}},
|
||||||
{"2bc1be74-1x9d-4300-a239-49a1196a045d", false},
|
{"2bc1be74-1x9d-4300-a239-49a1196a045d", false, [16]byte{}},
|
||||||
{"2bc1be74-169d-4300-a239-49a1196ag45d", false},
|
{"2bc1be74-169d-4300-a239-49a1196ag45d", false, [16]byte{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
func testValid(t *testing.T, in string, isUuid bool) {
|
func testValid(t *testing.T, in string, isUuid bool) {
|
||||||
|
@ -30,9 +31,22 @@ func testValid(t *testing.T, in string, isUuid bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testParse(t *testing.T, in string, expected [16]byte, isUuid bool) {
|
||||||
|
actual, err := Parse(in)
|
||||||
|
if isUuid && err != nil {
|
||||||
|
t.Errorf("Parse(%s) unexpected error %v", in, err)
|
||||||
|
} else if !isUuid && err == nil {
|
||||||
|
t.Errorf("Parse(%s) expected to fail but got %v", in, actual)
|
||||||
|
} else if actual != expected {
|
||||||
|
t.Errorf("Parse(%s) got %v expected %v", in, actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUUID(t *testing.T) {
|
func TestUUID(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
testValid(t, tt.in, tt.isUuid)
|
testValid(t, tt.in, tt.isUuid)
|
||||||
testValid(t, strings.ToUpper(tt.in), tt.isUuid)
|
testValid(t, strings.ToUpper(tt.in), tt.isUuid)
|
||||||
|
testParse(t, tt.in, tt.bytes, tt.isUuid)
|
||||||
|
testParse(t, strings.ToUpper(tt.in), tt.bytes, tt.isUuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ set search_path to camper, public;
|
||||||
|
|
||||||
select has_function('camper', 'draft_payment', array['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]']);
|
select has_function('camper', 'draft_payment', array['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]']);
|
||||||
select function_lang_is('camper', 'draft_payment', array['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]'], 'plpgsql');
|
select function_lang_is('camper', 'draft_payment', array['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]'], 'plpgsql');
|
||||||
select function_returns('camper', 'draft_payment', array['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]'], 'uuid');
|
select function_returns('camper', 'draft_payment', array['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]'], 'payment');
|
||||||
select isnt_definer('camper', 'draft_payment', array['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]']);
|
select isnt_definer('camper', 'draft_payment', array['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]']);
|
||||||
select volatility_is('camper', 'draft_payment', array['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]'], 'volatile');
|
select volatility_is('camper', 'draft_payment', array['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]'], 'volatile');
|
||||||
select function_privs_are('camper', 'draft_payment', array ['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]'], 'guest', array['EXECUTE']);
|
select function_privs_are('camper', 'draft_payment', array ['uuid', 'date', 'date', 'uuid', 'integer', 'integer', 'integer', 'integer', 'text', 'option_units[]'], 'guest', array['EXECUTE']);
|
||||||
|
|
Loading…
Reference in New Issue