Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
package pkg
import (
"context"
"errors"
2023-02-03 11:30:56 +00:00
"github.com/julienschmidt/httprouter"
2023-02-01 13:15:02 +00:00
"html/template"
2023-05-31 18:01:00 +00:00
"math"
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
"net/http"
"net/url"
2023-01-28 13:18:58 +00:00
"strconv"
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
)
const (
ContextCompanyKey = "numerus-company"
)
type Company struct {
Convert from cents to “price” and back
I do not want to use floats in the Go lang application, because it is
not supposed to do anything with these values other than to print and
retrieve them from the user; all computations will be performed by
PostgreSQL in cents.
That means i have to “convert” from the price format that users expect
to see (e.g., 1.234,56) to cents (e.g., 123456) and back when passing
data between Go and PostgreSQL, and that conversion depends on the
currency’s decimal places.
At first i did everything in Go, but saw that i would need to do it in
a loop when retrieving the list of products, and immediately knew it was
a mistake—i needed a PL/pgSQL function for that.
I still need to convert from string to float, however, when printing the
value to the user. Because the string representation is in C, but i
need to format it according to the locale with golang/x/text. That
package has the information of how to correctly format numbers, but it
is in an internal package that i can not use, and numbers.Digit only
accepts numeric types, not a string.
2023-02-05 12:55:12 +00:00
Id int
CurrencySymbol string
DecimalDigits int
Slug string
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
}
2023-02-03 11:30:56 +00:00
func CompanyHandler ( next http . Handler ) httprouter . Handle {
return func ( w http . ResponseWriter , r * http . Request , params httprouter . Params ) {
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
company := & Company {
2023-02-03 11:30:56 +00:00
Slug : params [ 0 ] . Value ,
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
}
2023-02-03 11:30:56 +00:00
conn := getConn ( r )
Convert from cents to “price” and back
I do not want to use floats in the Go lang application, because it is
not supposed to do anything with these values other than to print and
retrieve them from the user; all computations will be performed by
PostgreSQL in cents.
That means i have to “convert” from the price format that users expect
to see (e.g., 1.234,56) to cents (e.g., 123456) and back when passing
data between Go and PostgreSQL, and that conversion depends on the
currency’s decimal places.
At first i did everything in Go, but saw that i would need to do it in
a loop when retrieving the list of products, and immediately knew it was
a mistake—i needed a PL/pgSQL function for that.
I still need to convert from string to float, however, when printing the
value to the user. Because the string representation is in C, but i
need to format it according to the locale with golang/x/text. That
package has the information of how to correctly format numbers, but it
is in an internal package that i can not use, and numbers.Digit only
accepts numeric types, not a string.
2023-02-05 12:55:12 +00:00
err := conn . QueryRow ( r . Context ( ) , "select company_id, currency_symbol, decimal_digits from company join currency using (currency_code) where slug = $1" , company . Slug ) . Scan ( & company . Id , & company . CurrencySymbol , & company . DecimalDigits )
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
if err != nil {
http . NotFound ( w , r )
return
}
ctx := context . WithValue ( r . Context ( ) , ContextCompanyKey , company )
r = r . WithContext ( ctx )
2023-02-03 11:30:56 +00:00
r2 := new ( http . Request )
* r2 = * r
r2 . URL = new ( url . URL )
* r2 . URL = * r . URL
r2 . URL . Path = params [ 1 ] . Value
next . ServeHTTP ( w , r2 )
}
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
}
Convert from cents to “price” and back
I do not want to use floats in the Go lang application, because it is
not supposed to do anything with these values other than to print and
retrieve them from the user; all computations will be performed by
PostgreSQL in cents.
That means i have to “convert” from the price format that users expect
to see (e.g., 1.234,56) to cents (e.g., 123456) and back when passing
data between Go and PostgreSQL, and that conversion depends on the
currency’s decimal places.
At first i did everything in Go, but saw that i would need to do it in
a loop when retrieving the list of products, and immediately knew it was
a mistake—i needed a PL/pgSQL function for that.
I still need to convert from string to float, however, when printing the
value to the user. Because the string representation is in C, but i
need to format it according to the locale with golang/x/text. That
package has the information of how to correctly format numbers, but it
is in an internal package that i can not use, and numbers.Digit only
accepts numeric types, not a string.
2023-02-05 12:55:12 +00:00
func ( c Company ) MinCents ( ) float64 {
var r float64
r = 1
for i := 0 ; i < c . DecimalDigits ; i ++ {
r /= 10.0
}
return r
}
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
func getCompany ( r * http . Request ) * Company {
company := r . Context ( ) . Value ( ContextCompanyKey )
if company == nil {
return nil
}
return company . ( * Company )
}
2023-01-28 11:24:52 +00:00
type CurrencyOption struct {
Code string
Symbol string
}
2023-01-27 20:30:14 +00:00
type CountryOption struct {
Code string
Name string
}
2023-01-28 13:18:58 +00:00
type Tax struct {
2023-02-28 11:02:27 +00:00
Id int
Name string
Class string
Rate int
2023-01-28 13:18:58 +00:00
}
2023-03-03 15:49:06 +00:00
type PaymentMethod struct {
Id int
Name string
Instructions string
}
2023-02-01 13:15:02 +00:00
type taxDetailsForm struct {
Split contact relation into tax_details, phone, web, and email
We need to have contacts with just a name: we need to assign
freelancer’s quote as expense linked the government, but of course we
do not have a phone or email for that “contact”, much less a VATIN or
other tax details.
It is also interesting for other expenses-only contacts to not have to
input all tax details, as we may not need to invoice then, thus are
useless for us, but sometimes it might be interesting to have them,
“just in case”.
Of course, i did not want to make nullable any of the tax details
required to generate an invoice, otherwise we could allow illegal
invoices. Therefore, that data had to go in a different relation,
and invoice’s foreign key update to point to that relation, not just
customer, or we would again be able to create invalid invoices.
We replaced the contact’s trade name with just name, because we do not
need _three_ names for a contact, but we _do_ need two: the one we use
to refer to them and the business name for tax purposes.
The new contact_phone, contact_web, and contact_email relations could be
simply a nullable field, but i did not see the point, since there are
not that many instances where i need any of this data.
Now company.taxDetailsForm is no longer “the same as contactForm with
some extra fields”, because i have to add a check whether the user needs
to invoice the contact, to check that the required values are there.
I have an additional problem with the contact form when not using
JavaScript: i must set the required field to all tax details fields to
avoid the “(optional)” suffix, and because they _are_ required when
that checkbox is enabled, but i can not set them optional when the check
is unchecked. My solution for now is to ignore the form validation,
and later i will add some JavaScript that adds the validation again,
so it will work in all cases.
2023-06-30 19:32:48 +00:00
locale * Locale
TradeName * InputField
BusinessName * InputField
VATIN * InputField
Phone * InputField
Email * InputField
Web * InputField
Address * InputField
City * InputField
Province * InputField
PostalCode * InputField
Country * SelectField
2023-03-02 09:24:44 +00:00
Currency * SelectField
InvoiceNumberFormat * InputField
2023-05-31 18:01:00 +00:00
NextInvoiceNumber * InputField
2023-06-09 10:43:50 +00:00
QuoteNumberFormat * InputField
NextQuoteNumber * InputField
2023-03-02 09:24:44 +00:00
LegalDisclaimer * InputField
2023-02-01 13:15:02 +00:00
}
func newTaxDetailsForm ( ctx context . Context , conn * Conn , locale * Locale ) * taxDetailsForm {
return & taxDetailsForm {
Split contact relation into tax_details, phone, web, and email
We need to have contacts with just a name: we need to assign
freelancer’s quote as expense linked the government, but of course we
do not have a phone or email for that “contact”, much less a VATIN or
other tax details.
It is also interesting for other expenses-only contacts to not have to
input all tax details, as we may not need to invoice then, thus are
useless for us, but sometimes it might be interesting to have them,
“just in case”.
Of course, i did not want to make nullable any of the tax details
required to generate an invoice, otherwise we could allow illegal
invoices. Therefore, that data had to go in a different relation,
and invoice’s foreign key update to point to that relation, not just
customer, or we would again be able to create invalid invoices.
We replaced the contact’s trade name with just name, because we do not
need _three_ names for a contact, but we _do_ need two: the one we use
to refer to them and the business name for tax purposes.
The new contact_phone, contact_web, and contact_email relations could be
simply a nullable field, but i did not see the point, since there are
not that many instances where i need any of this data.
Now company.taxDetailsForm is no longer “the same as contactForm with
some extra fields”, because i have to add a check whether the user needs
to invoice the contact, to check that the required values are there.
I have an additional problem with the contact form when not using
JavaScript: i must set the required field to all tax details fields to
avoid the “(optional)” suffix, and because they _are_ required when
that checkbox is enabled, but i can not set them optional when the check
is unchecked. My solution for now is to ignore the form validation,
and later i will add some JavaScript that adds the validation again,
so it will work in all cases.
2023-06-30 19:32:48 +00:00
locale : locale ,
TradeName : & InputField {
Name : "trade_name" ,
Label : pgettext ( "input" , "Trade name" , locale ) ,
Type : "text" ,
} ,
Phone : & InputField {
Name : "phone" ,
Label : pgettext ( "input" , "Phone" , locale ) ,
Type : "tel" ,
Required : true ,
Attributes : [ ] template . HTMLAttr {
` autocomplete="tel" ` ,
} ,
} ,
Email : & InputField {
Name : "email" ,
Label : pgettext ( "input" , "Email" , locale ) ,
Type : "email" ,
Required : true ,
Attributes : [ ] template . HTMLAttr {
` autocomplete="email" ` ,
} ,
} ,
Web : & InputField {
Name : "web" ,
Label : pgettext ( "input" , "Web" , locale ) ,
Type : "url" ,
Attributes : [ ] template . HTMLAttr {
` autocomplete="url" ` ,
} ,
} ,
BusinessName : & InputField {
Name : "business_name" ,
Label : pgettext ( "input" , "Business name" , locale ) ,
Type : "text" ,
Required : true ,
Attributes : [ ] template . HTMLAttr {
` autocomplete="organization" ` ,
` minlength="2" ` ,
} ,
} ,
VATIN : & InputField {
Name : "vatin" ,
Label : pgettext ( "input" , "VAT number" , locale ) ,
Type : "text" ,
Required : true ,
} ,
Address : & InputField {
Name : "address" ,
Label : pgettext ( "input" , "Address" , locale ) ,
Type : "text" ,
Required : true ,
Attributes : [ ] template . HTMLAttr {
` autocomplete="address-line1" ` ,
} ,
} ,
City : & InputField {
Name : "city" ,
Label : pgettext ( "input" , "City" , locale ) ,
Type : "text" ,
Required : true ,
} ,
Province : & InputField {
Name : "province" ,
Label : pgettext ( "input" , "Province" , locale ) ,
Type : "text" ,
Required : true ,
} ,
PostalCode : & InputField {
Name : "postal_code" ,
Label : pgettext ( "input" , "Postal code" , locale ) ,
Type : "text" ,
Required : true ,
Attributes : [ ] template . HTMLAttr {
` autocomplete="postal-code" ` ,
} ,
} ,
Country : & SelectField {
Name : "country" ,
Label : pgettext ( "input" , "Country" , locale ) ,
Options : mustGetCountryOptions ( ctx , conn , locale ) ,
Required : true ,
Selected : [ ] string { "ES" } ,
Attributes : [ ] template . HTMLAttr {
` autocomplete="country" ` ,
} ,
} ,
2023-02-01 13:15:02 +00:00
Currency : & SelectField {
Name : "currency" ,
Label : pgettext ( "input" , "Currency" , locale ) ,
Options : MustGetOptions ( ctx , conn , "select currency_code, currency_symbol from currency order by currency_code" ) ,
2023-02-05 13:06:33 +00:00
Required : true ,
2023-02-08 12:47:36 +00:00
Selected : [ ] string { "EUR" } ,
2023-02-01 13:15:02 +00:00
} ,
2023-03-02 09:24:44 +00:00
InvoiceNumberFormat : & InputField {
Name : "invoice_number_format" ,
Label : pgettext ( "input" , "Invoice number format" , locale ) ,
Type : "text" ,
Required : true ,
} ,
2023-05-31 18:01:00 +00:00
NextInvoiceNumber : & InputField {
Name : "next_invoice_number" ,
Label : pgettext ( "input" , "Next invoice number" , locale ) ,
Type : "number" ,
Required : true ,
Attributes : [ ] template . HTMLAttr {
"min=1" ,
} ,
} ,
2023-06-09 10:43:50 +00:00
QuoteNumberFormat : & InputField {
Name : "quote_number_format" ,
Label : pgettext ( "input" , "Quotation number format" , locale ) ,
Type : "text" ,
Required : true ,
} ,
NextQuoteNumber : & InputField {
Name : "next_quotation_number" ,
Label : pgettext ( "input" , "Next quotation number" , locale ) ,
Type : "number" ,
Required : true ,
Attributes : [ ] template . HTMLAttr {
"min=1" ,
} ,
} ,
2023-03-02 09:24:44 +00:00
LegalDisclaimer : & InputField {
Name : "legal_disclaimer" ,
Label : pgettext ( "input" , "Legal disclaimer" , locale ) ,
Type : "textarea" ,
} ,
2023-02-01 13:15:02 +00:00
}
}
func ( form * taxDetailsForm ) Parse ( r * http . Request ) error {
Split contact relation into tax_details, phone, web, and email
We need to have contacts with just a name: we need to assign
freelancer’s quote as expense linked the government, but of course we
do not have a phone or email for that “contact”, much less a VATIN or
other tax details.
It is also interesting for other expenses-only contacts to not have to
input all tax details, as we may not need to invoice then, thus are
useless for us, but sometimes it might be interesting to have them,
“just in case”.
Of course, i did not want to make nullable any of the tax details
required to generate an invoice, otherwise we could allow illegal
invoices. Therefore, that data had to go in a different relation,
and invoice’s foreign key update to point to that relation, not just
customer, or we would again be able to create invalid invoices.
We replaced the contact’s trade name with just name, because we do not
need _three_ names for a contact, but we _do_ need two: the one we use
to refer to them and the business name for tax purposes.
The new contact_phone, contact_web, and contact_email relations could be
simply a nullable field, but i did not see the point, since there are
not that many instances where i need any of this data.
Now company.taxDetailsForm is no longer “the same as contactForm with
some extra fields”, because i have to add a check whether the user needs
to invoice the contact, to check that the required values are there.
I have an additional problem with the contact form when not using
JavaScript: i must set the required field to all tax details fields to
avoid the “(optional)” suffix, and because they _are_ required when
that checkbox is enabled, but i can not set them optional when the check
is unchecked. My solution for now is to ignore the form validation,
and later i will add some JavaScript that adds the validation again,
so it will work in all cases.
2023-06-30 19:32:48 +00:00
if err := r . ParseForm ( ) ; err != nil {
2023-02-01 13:15:02 +00:00
return err
}
Split contact relation into tax_details, phone, web, and email
We need to have contacts with just a name: we need to assign
freelancer’s quote as expense linked the government, but of course we
do not have a phone or email for that “contact”, much less a VATIN or
other tax details.
It is also interesting for other expenses-only contacts to not have to
input all tax details, as we may not need to invoice then, thus are
useless for us, but sometimes it might be interesting to have them,
“just in case”.
Of course, i did not want to make nullable any of the tax details
required to generate an invoice, otherwise we could allow illegal
invoices. Therefore, that data had to go in a different relation,
and invoice’s foreign key update to point to that relation, not just
customer, or we would again be able to create invalid invoices.
We replaced the contact’s trade name with just name, because we do not
need _three_ names for a contact, but we _do_ need two: the one we use
to refer to them and the business name for tax purposes.
The new contact_phone, contact_web, and contact_email relations could be
simply a nullable field, but i did not see the point, since there are
not that many instances where i need any of this data.
Now company.taxDetailsForm is no longer “the same as contactForm with
some extra fields”, because i have to add a check whether the user needs
to invoice the contact, to check that the required values are there.
I have an additional problem with the contact form when not using
JavaScript: i must set the required field to all tax details fields to
avoid the “(optional)” suffix, and because they _are_ required when
that checkbox is enabled, but i can not set them optional when the check
is unchecked. My solution for now is to ignore the form validation,
and later i will add some JavaScript that adds the validation again,
so it will work in all cases.
2023-06-30 19:32:48 +00:00
form . TradeName . FillValue ( r )
form . BusinessName . FillValue ( r )
form . VATIN . FillValue ( r )
form . Phone . FillValue ( r )
form . Email . FillValue ( r )
form . Web . FillValue ( r )
form . Address . FillValue ( r )
form . City . FillValue ( r )
form . Province . FillValue ( r )
form . PostalCode . FillValue ( r )
form . Country . FillValue ( r )
2023-02-01 13:15:02 +00:00
form . Currency . FillValue ( r )
2023-03-02 09:24:44 +00:00
form . InvoiceNumberFormat . FillValue ( r )
2023-05-31 18:01:00 +00:00
form . NextInvoiceNumber . FillValue ( r )
2023-06-09 10:43:50 +00:00
form . QuoteNumberFormat . FillValue ( r )
form . NextQuoteNumber . FillValue ( r )
2023-03-02 09:24:44 +00:00
form . LegalDisclaimer . FillValue ( r )
2023-02-01 13:15:02 +00:00
return nil
}
func ( form * taxDetailsForm ) Validate ( ctx context . Context , conn * Conn ) bool {
validator := newFormValidator ( )
Split contact relation into tax_details, phone, web, and email
We need to have contacts with just a name: we need to assign
freelancer’s quote as expense linked the government, but of course we
do not have a phone or email for that “contact”, much less a VATIN or
other tax details.
It is also interesting for other expenses-only contacts to not have to
input all tax details, as we may not need to invoice then, thus are
useless for us, but sometimes it might be interesting to have them,
“just in case”.
Of course, i did not want to make nullable any of the tax details
required to generate an invoice, otherwise we could allow illegal
invoices. Therefore, that data had to go in a different relation,
and invoice’s foreign key update to point to that relation, not just
customer, or we would again be able to create invalid invoices.
We replaced the contact’s trade name with just name, because we do not
need _three_ names for a contact, but we _do_ need two: the one we use
to refer to them and the business name for tax purposes.
The new contact_phone, contact_web, and contact_email relations could be
simply a nullable field, but i did not see the point, since there are
not that many instances where i need any of this data.
Now company.taxDetailsForm is no longer “the same as contactForm with
some extra fields”, because i have to add a check whether the user needs
to invoice the contact, to check that the required values are there.
I have an additional problem with the contact form when not using
JavaScript: i must set the required field to all tax details fields to
avoid the “(optional)” suffix, and because they _are_ required when
that checkbox is enabled, but i can not set them optional when the check
is unchecked. My solution for now is to ignore the form validation,
and later i will add some JavaScript that adds the validation again,
so it will work in all cases.
2023-06-30 19:32:48 +00:00
country := ""
if validator . CheckValidSelectOption ( form . Country , gettext ( "Selected country is not valid." , form . locale ) ) {
country = form . Country . Selected [ 0 ]
}
validator . CheckRequiredInput ( form . BusinessName , gettext ( "Business name can not be empty." , form . locale ) )
validator . CheckInputMinLength ( form . BusinessName , 2 , gettext ( "Business name must have at least two letters." , form . locale ) )
if validator . CheckRequiredInput ( form . VATIN , gettext ( "VAT number can not be empty." , form . locale ) ) {
Create validation function for SQL domains and for phones
When i wrote the functions to import contact, i already created a couple
of “temporary” functions to validate whether the input given from the
Excel files was correct according to the various domains used in the
relations, so i can know whether i can import that data.
I realized that i could do exactly the same when validating forms: check
that the value conforms to the domain, in the exact same way, so i can
make sure that the value will be accepted without duplicating the logic,
at the expense of a call to the database.
In an ideal world, i would use pg_input_is_valid, but this function is
only available in PostgreSQL 16 and Debian 12 uses PostgreSQL 15.
These functions are in the public schema because initially i wanted to
use them to also validate email, which is needed in the login form, but
then i recanted and kept the same email validation in Go, because
something felt off about using the database for that particular form,
but i do not know why.
2023-07-03 09:31:59 +00:00
validator . CheckValidVATINInput ( ctx , conn , form . VATIN , country , gettext ( "This value is not a valid VAT number." , form . locale ) )
Split contact relation into tax_details, phone, web, and email
We need to have contacts with just a name: we need to assign
freelancer’s quote as expense linked the government, but of course we
do not have a phone or email for that “contact”, much less a VATIN or
other tax details.
It is also interesting for other expenses-only contacts to not have to
input all tax details, as we may not need to invoice then, thus are
useless for us, but sometimes it might be interesting to have them,
“just in case”.
Of course, i did not want to make nullable any of the tax details
required to generate an invoice, otherwise we could allow illegal
invoices. Therefore, that data had to go in a different relation,
and invoice’s foreign key update to point to that relation, not just
customer, or we would again be able to create invalid invoices.
We replaced the contact’s trade name with just name, because we do not
need _three_ names for a contact, but we _do_ need two: the one we use
to refer to them and the business name for tax purposes.
The new contact_phone, contact_web, and contact_email relations could be
simply a nullable field, but i did not see the point, since there are
not that many instances where i need any of this data.
Now company.taxDetailsForm is no longer “the same as contactForm with
some extra fields”, because i have to add a check whether the user needs
to invoice the contact, to check that the required values are there.
I have an additional problem with the contact form when not using
JavaScript: i must set the required field to all tax details fields to
avoid the “(optional)” suffix, and because they _are_ required when
that checkbox is enabled, but i can not set them optional when the check
is unchecked. My solution for now is to ignore the form validation,
and later i will add some JavaScript that adds the validation again,
so it will work in all cases.
2023-06-30 19:32:48 +00:00
}
if validator . CheckRequiredInput ( form . Phone , gettext ( "Phone can not be empty." , form . locale ) ) {
Create validation function for SQL domains and for phones
When i wrote the functions to import contact, i already created a couple
of “temporary” functions to validate whether the input given from the
Excel files was correct according to the various domains used in the
relations, so i can know whether i can import that data.
I realized that i could do exactly the same when validating forms: check
that the value conforms to the domain, in the exact same way, so i can
make sure that the value will be accepted without duplicating the logic,
at the expense of a call to the database.
In an ideal world, i would use pg_input_is_valid, but this function is
only available in PostgreSQL 16 and Debian 12 uses PostgreSQL 15.
These functions are in the public schema because initially i wanted to
use them to also validate email, which is needed in the login form, but
then i recanted and kept the same email validation in Go, because
something felt off about using the database for that particular form,
but i do not know why.
2023-07-03 09:31:59 +00:00
validator . CheckValidPhoneInput ( ctx , conn , form . Phone , country , gettext ( "This value is not a valid phone number." , form . locale ) )
Split contact relation into tax_details, phone, web, and email
We need to have contacts with just a name: we need to assign
freelancer’s quote as expense linked the government, but of course we
do not have a phone or email for that “contact”, much less a VATIN or
other tax details.
It is also interesting for other expenses-only contacts to not have to
input all tax details, as we may not need to invoice then, thus are
useless for us, but sometimes it might be interesting to have them,
“just in case”.
Of course, i did not want to make nullable any of the tax details
required to generate an invoice, otherwise we could allow illegal
invoices. Therefore, that data had to go in a different relation,
and invoice’s foreign key update to point to that relation, not just
customer, or we would again be able to create invalid invoices.
We replaced the contact’s trade name with just name, because we do not
need _three_ names for a contact, but we _do_ need two: the one we use
to refer to them and the business name for tax purposes.
The new contact_phone, contact_web, and contact_email relations could be
simply a nullable field, but i did not see the point, since there are
not that many instances where i need any of this data.
Now company.taxDetailsForm is no longer “the same as contactForm with
some extra fields”, because i have to add a check whether the user needs
to invoice the contact, to check that the required values are there.
I have an additional problem with the contact form when not using
JavaScript: i must set the required field to all tax details fields to
avoid the “(optional)” suffix, and because they _are_ required when
that checkbox is enabled, but i can not set them optional when the check
is unchecked. My solution for now is to ignore the form validation,
and later i will add some JavaScript that adds the validation again,
so it will work in all cases.
2023-06-30 19:32:48 +00:00
}
if validator . CheckRequiredInput ( form . Email , gettext ( "Email can not be empty." , form . locale ) ) {
validator . CheckValidEmailInput ( form . Email , gettext ( "This value is not a valid email. It should be like name@domain.com." , form . locale ) )
}
if form . Web . Val != "" {
validator . CheckValidURL ( form . Web , gettext ( "This value is not a valid web address. It should be like https://domain.com/." , form . locale ) )
}
validator . CheckRequiredInput ( form . Address , gettext ( "Address can not be empty." , form . locale ) )
validator . CheckRequiredInput ( form . City , gettext ( "City can not be empty." , form . locale ) )
validator . CheckRequiredInput ( form . Province , gettext ( "Province can not be empty." , form . locale ) )
if validator . CheckRequiredInput ( form . PostalCode , gettext ( "Postal code can not be empty." , form . locale ) ) {
validator . CheckValidPostalCode ( ctx , conn , form . PostalCode , country , gettext ( "This value is not a valid postal code." , form . locale ) )
}
2023-02-01 13:15:02 +00:00
validator . CheckValidSelectOption ( form . Currency , gettext ( "Selected currency is not valid." , form . locale ) )
2023-03-02 09:24:44 +00:00
validator . CheckRequiredInput ( form . InvoiceNumberFormat , gettext ( "Invoice number format can not be empty." , form . locale ) )
2023-05-31 18:01:00 +00:00
validator . CheckValidInteger ( form . NextInvoiceNumber , 1 , math . MaxInt32 , gettext ( "Next invoice number must be a number greater than zero." , form . locale ) )
2023-06-09 10:43:50 +00:00
validator . CheckRequiredInput ( form . QuoteNumberFormat , gettext ( "Quotation number format can not be empty." , form . locale ) )
validator . CheckValidInteger ( form . NextQuoteNumber , 1 , math . MaxInt32 , gettext ( "Next quotation number must be a number greater than zero." , form . locale ) )
Split contact relation into tax_details, phone, web, and email
We need to have contacts with just a name: we need to assign
freelancer’s quote as expense linked the government, but of course we
do not have a phone or email for that “contact”, much less a VATIN or
other tax details.
It is also interesting for other expenses-only contacts to not have to
input all tax details, as we may not need to invoice then, thus are
useless for us, but sometimes it might be interesting to have them,
“just in case”.
Of course, i did not want to make nullable any of the tax details
required to generate an invoice, otherwise we could allow illegal
invoices. Therefore, that data had to go in a different relation,
and invoice’s foreign key update to point to that relation, not just
customer, or we would again be able to create invalid invoices.
We replaced the contact’s trade name with just name, because we do not
need _three_ names for a contact, but we _do_ need two: the one we use
to refer to them and the business name for tax purposes.
The new contact_phone, contact_web, and contact_email relations could be
simply a nullable field, but i did not see the point, since there are
not that many instances where i need any of this data.
Now company.taxDetailsForm is no longer “the same as contactForm with
some extra fields”, because i have to add a check whether the user needs
to invoice the contact, to check that the required values are there.
I have an additional problem with the contact form when not using
JavaScript: i must set the required field to all tax details fields to
avoid the “(optional)” suffix, and because they _are_ required when
that checkbox is enabled, but i can not set them optional when the check
is unchecked. My solution for now is to ignore the form validation,
and later i will add some JavaScript that adds the validation again,
so it will work in all cases.
2023-06-30 19:32:48 +00:00
return validator . AllOK ( )
2023-02-01 13:15:02 +00:00
}
func ( form * taxDetailsForm ) mustFillFromDatabase ( ctx context . Context , conn * Conn , company * Company ) * taxDetailsForm {
2023-05-31 18:01:00 +00:00
err := conn . QueryRow ( ctx , `
select business_name
, substr ( vatin : : text , 3 )
, trade_name
, phone
, email
, web
, address
, city
, province
, postal_code
, country_code
, currency_code
, invoice_number_format
2023-06-09 10:43:50 +00:00
, quote_number_format
2023-05-31 18:01:00 +00:00
, legal_disclaimer
, coalesce ( invoice_number_counter . currval , 0 ) + 1
2023-06-09 10:43:50 +00:00
, coalesce ( quote_number_counter . currval , 0 ) + 1
2023-05-31 18:01:00 +00:00
from company
left join invoice_number_counter
on invoice_number_counter . company_id = company . company_id
2023-06-09 10:43:50 +00:00
and invoice_number_counter . year = date_part ( ' year ' , current_date )
left join quote_number_counter
on quote_number_counter . company_id = company . company_id
and quote_number_counter . year = date_part ( ' year ' , current_date )
2023-05-31 18:01:00 +00:00
where company . company_id = $ 1 ` , company . Id ) . Scan (
form . BusinessName ,
form . VATIN ,
form . TradeName ,
form . Phone ,
form . Email ,
form . Web ,
form . Address ,
form . City ,
form . Province ,
form . PostalCode ,
form . Country ,
form . Currency ,
form . InvoiceNumberFormat ,
2023-06-09 10:43:50 +00:00
form . QuoteNumberFormat ,
2023-05-31 18:01:00 +00:00
form . LegalDisclaimer ,
form . NextInvoiceNumber ,
2023-06-09 10:43:50 +00:00
form . NextQuoteNumber ,
2023-05-31 18:01:00 +00:00
)
if err != nil {
panic ( err )
}
2023-02-01 13:15:02 +00:00
return form
}
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
type TaxDetailsPage struct {
2023-03-03 15:49:06 +00:00
DetailsForm * taxDetailsForm
NewTaxForm * taxForm
Taxes [ ] * Tax
NewPaymentMethodForm * paymentMethodForm
PaymentMethods [ ] * PaymentMethod
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
}
2023-02-03 11:30:56 +00:00
func GetCompanyTaxDetailsForm ( w http . ResponseWriter , r * http . Request , _ httprouter . Params ) {
mustRenderTaxDetailsForm ( w , r , newTaxDetailsFormFromDatabase ( r ) )
}
func newTaxDetailsFormFromDatabase ( r * http . Request ) * taxDetailsForm {
locale := getLocale ( r )
conn := getConn ( r )
form := newTaxDetailsForm ( r . Context ( ) , conn , locale )
company := mustGetCompany ( r )
form . mustFillFromDatabase ( r . Context ( ) , conn , company )
return form
}
func HandleCompanyTaxDetailsForm ( w http . ResponseWriter , r * http . Request , _ httprouter . Params ) {
locale := getLocale ( r )
conn := getConn ( r )
form := newTaxDetailsForm ( r . Context ( ) , conn , locale )
if err := form . Parse ( r ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
if err := verifyCsrfTokenValid ( r ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusForbidden )
return
}
if ok := form . Validate ( r . Context ( ) , conn ) ; ! ok {
2023-03-21 10:58:54 +00:00
if ! IsHTMxRequest ( r ) {
w . WriteHeader ( http . StatusUnprocessableEntity )
}
2023-02-03 11:30:56 +00:00
mustRenderTaxDetailsForm ( w , r , form )
return
}
company := mustGetCompany ( r )
2023-06-09 10:43:50 +00:00
tx := conn . MustBegin ( r . Context ( ) )
defer tx . MustRollback ( r . Context ( ) )
tx . MustExec ( r . Context ( ) , `
update company
set business_name = $ 1
, vatin = ( $ 11 || $ 2 ) : : vatin
, trade_name = $ 3
, phone = parse_packed_phone_number ( $ 4 , $ 11 )
, email = $ 5
, web = $ 6
, address = $ 7
, city = $ 8
, province = $ 9
, postal_code = $ 10
, country_code = $ 11
, currency_code = $ 12
, invoice_number_format = $ 13
, quote_number_format = $ 14
, legal_disclaimer = $ 15
where company_id = $ 16
` ,
form . BusinessName ,
form . VATIN ,
form . TradeName ,
form . Phone ,
form . Email ,
form . Web ,
form . Address ,
form . City ,
form . Province ,
form . PostalCode ,
form . Country ,
form . Currency ,
form . InvoiceNumberFormat ,
form . QuoteNumberFormat ,
form . LegalDisclaimer ,
company . Id )
tx . MustExec ( r . Context ( ) , `
insert into invoice_number_counter ( company_id , year , currval )
values ( $ 1 , date_part ( ' year ' , current_date ) , $ 2 )
on conflict ( company_id , year ) do update
set currval = excluded . currval
` ,
company . Id ,
form . NextInvoiceNumber . Integer ( ) - 1 )
tx . MustExec ( r . Context ( ) , `
insert into quote_number_counter ( company_id , year , currval )
values ( $ 1 , date_part ( ' year ' , current_date ) , $ 2 )
on conflict ( company_id , year ) do update
set currval = excluded . currval
` ,
company . Id ,
form . NextQuoteNumber . Integer ( ) - 1 )
tx . MustCommit ( r . Context ( ) )
2023-03-21 10:58:54 +00:00
if IsHTMxRequest ( r ) {
2023-04-06 10:07:20 +00:00
w . Header ( ) . Set ( HxTrigger , "closeModal" )
2023-03-21 10:58:54 +00:00
w . WriteHeader ( http . StatusNoContent )
} else {
http . Redirect ( w , r , companyURI ( company , "/tax-details" ) , http . StatusSeeOther )
}
2023-02-03 11:30:56 +00:00
}
func mustRenderTaxDetailsForm ( w http . ResponseWriter , r * http . Request , form * taxDetailsForm ) {
2023-03-03 15:49:06 +00:00
conn := getConn ( r )
locale := getLocale ( r )
2023-02-03 11:30:56 +00:00
page := & TaxDetailsPage {
2023-03-03 15:49:06 +00:00
DetailsForm : form ,
NewTaxForm : newTaxForm ( r . Context ( ) , conn , mustGetCompany ( r ) , locale ) ,
NewPaymentMethodForm : newPaymentMethodForm ( locale ) ,
2023-02-03 11:30:56 +00:00
}
mustRenderTexDetailsPage ( w , r , page )
}
func mustRenderTaxForm ( w http . ResponseWriter , r * http . Request , form * taxForm ) {
page := & TaxDetailsPage {
2023-03-03 15:49:06 +00:00
DetailsForm : newTaxDetailsFormFromDatabase ( r ) ,
NewTaxForm : form ,
NewPaymentMethodForm : newPaymentMethodForm ( getLocale ( r ) ) ,
}
mustRenderTexDetailsPage ( w , r , page )
}
func mustRenderPaymentMethodForm ( w http . ResponseWriter , r * http . Request , form * paymentMethodForm ) {
page := & TaxDetailsPage {
DetailsForm : newTaxDetailsFormFromDatabase ( r ) ,
NewTaxForm : newTaxForm ( r . Context ( ) , getConn ( r ) , mustGetCompany ( r ) , getLocale ( r ) ) ,
NewPaymentMethodForm : form ,
2023-02-03 11:30:56 +00:00
}
mustRenderTexDetailsPage ( w , r , page )
}
func mustRenderTexDetailsPage ( w http . ResponseWriter , r * http . Request , page * TaxDetailsPage ) {
conn := getConn ( r )
company := mustGetCompany ( r )
page . Taxes = mustGetTaxes ( r . Context ( ) , conn , company )
2023-03-03 15:49:06 +00:00
page . PaymentMethods = mustCollectPaymentMethods ( r . Context ( ) , conn , company )
2023-03-21 10:58:54 +00:00
mustRenderModalTemplate ( w , r , "tax-details.gohtml" , page )
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
}
func mustGetCompany ( r * http . Request ) * Company {
company := getCompany ( r )
if company == nil {
panic ( errors . New ( "company: required but not found" ) )
}
2023-01-27 00:08:03 +00:00
return company
Add the company relation and read-only form to edit
I do not have more time to update the update to the company today, but i
believe this is already a good amount of work for a commit.
The company is going to be used for row level security, as users will
only have access to the data from companies they are granted access, by
virtue of being in the company_user relation.
I did not know how add a row level security policy to the company_user
because i needed the to select on the same relation and this is not
allowed, because it would create an infinite loop.
Had to add the vat, pg_libphonenumber, and uri extensions in order to
validate VAT identification numbers, phone numbers, and URIs,
repectively. These libraries are not in Debian, but i created packages
for them all in https://dev.tandem.ws/tandem.
2023-01-24 20:46:07 +00:00
}
2023-01-27 20:30:14 +00:00
2023-02-01 13:15:02 +00:00
func mustGetTaxes ( ctx context . Context , conn * Conn , company * Company ) [ ] * Tax {
2023-02-28 11:02:27 +00:00
rows , err := conn . Query ( ctx , "select tax_id, tax.name, tax_class.name, (rate * 100)::integer from tax join tax_class using (tax_class_id) where tax.company_id = $1 order by rate, tax.name" , company . Id )
2023-01-27 20:30:14 +00:00
if err != nil {
panic ( err )
}
defer rows . Close ( )
2023-02-01 13:15:02 +00:00
var taxes [ ] * Tax
2023-01-27 20:30:14 +00:00
for rows . Next ( ) {
2023-02-01 13:15:02 +00:00
tax := & Tax { }
2023-02-28 11:02:27 +00:00
err = rows . Scan ( & tax . Id , & tax . Name , & tax . Class , & tax . Rate )
2023-01-27 20:30:14 +00:00
if err != nil {
panic ( err )
}
2023-02-01 13:15:02 +00:00
taxes = append ( taxes , tax )
2023-01-27 20:30:14 +00:00
}
if rows . Err ( ) != nil {
panic ( rows . Err ( ) )
}
2023-02-01 13:15:02 +00:00
return taxes
2023-01-27 20:30:14 +00:00
}
2023-01-28 11:24:52 +00:00
2023-03-03 15:49:06 +00:00
func mustCollectPaymentMethods ( ctx context . Context , conn * Conn , company * Company ) [ ] * PaymentMethod {
rows , err := conn . Query ( ctx , "select payment_method_id, name, instructions from payment_method where company_id = $1 order by name" , company . Id )
if err != nil {
panic ( err )
}
defer rows . Close ( )
var methods [ ] * PaymentMethod
for rows . Next ( ) {
method := & PaymentMethod { }
err = rows . Scan ( & method . Id , & method . Name , & method . Instructions )
if err != nil {
panic ( err )
}
methods = append ( methods , method )
}
if rows . Err ( ) != nil {
panic ( rows . Err ( ) )
}
return methods
}
2023-02-03 11:30:56 +00:00
type taxForm struct {
2023-02-01 13:15:02 +00:00
locale * Locale
Name * InputField
2023-02-28 11:02:27 +00:00
Class * SelectField
2023-02-01 13:15:02 +00:00
Rate * InputField
}
2023-01-28 11:24:52 +00:00
2023-02-28 11:02:27 +00:00
func newTaxForm ( ctx context . Context , conn * Conn , company * Company , locale * Locale ) * taxForm {
2023-02-03 11:30:56 +00:00
return & taxForm {
2023-02-01 13:15:02 +00:00
locale : locale ,
Name : & InputField {
Name : "tax_name" ,
Label : pgettext ( "input" , "Tax name" , locale ) ,
Type : "text" ,
Required : true ,
} ,
2023-02-28 11:02:27 +00:00
Class : & SelectField {
Name : "tax_class" ,
Label : pgettext ( "input" , "Tax Class" , locale ) ,
Options : MustGetOptions ( ctx , conn , "select tax_class_id::text, name from tax_class where company_id = $1 order by name" , company . Id ) ,
Required : true ,
EmptyLabel : gettext ( "Select a tax class" , locale ) ,
} ,
2023-02-01 13:15:02 +00:00
Rate : & InputField {
Name : "tax_rate" ,
Label : pgettext ( "input" , "Rate (%)" , locale ) ,
Type : "number" ,
Required : true ,
Attributes : [ ] template . HTMLAttr {
"min=-99" ,
"max=99" ,
} ,
} ,
2023-01-28 11:24:52 +00:00
}
}
2023-01-28 13:18:58 +00:00
2023-02-03 11:30:56 +00:00
func ( form * taxForm ) Parse ( r * http . Request ) error {
2023-02-01 13:15:02 +00:00
if err := r . ParseForm ( ) ; err != nil {
return err
2023-01-28 13:18:58 +00:00
}
2023-02-01 13:15:02 +00:00
form . Name . FillValue ( r )
2023-02-28 11:02:27 +00:00
form . Class . FillValue ( r )
2023-02-01 13:15:02 +00:00
form . Rate . FillValue ( r )
return nil
}
2023-01-28 13:18:58 +00:00
2023-02-03 11:30:56 +00:00
func ( form * taxForm ) Validate ( ) bool {
2023-02-01 13:15:02 +00:00
validator := newFormValidator ( )
validator . CheckRequiredInput ( form . Name , gettext ( "Tax name can not be empty." , form . locale ) )
2023-02-28 11:02:27 +00:00
validator . CheckValidSelectOption ( form . Class , gettext ( "Selected tax class is not valid." , form . locale ) )
2023-02-01 13:15:02 +00:00
if validator . CheckRequiredInput ( form . Rate , gettext ( "Tax rate can not be empty." , form . locale ) ) {
validator . CheckValidInteger ( form . Rate , - 99 , 99 , gettext ( "Tax rate must be an integer between -99 and 99." , form . locale ) )
2023-01-28 13:18:58 +00:00
}
2023-02-01 13:15:02 +00:00
return validator . AllOK ( )
2023-01-28 13:18:58 +00:00
}
2023-02-03 11:30:56 +00:00
func HandleDeleteCompanyTax ( w http . ResponseWriter , r * http . Request , params httprouter . Params ) {
taxId , err := strconv . Atoi ( params [ 0 ] . Value )
if err != nil {
http . NotFound ( w , r )
return
}
if err := verifyCsrfTokenValid ( r ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusForbidden )
return
}
conn := getConn ( r )
conn . MustExec ( r . Context ( ) , "delete from tax where tax_id = $1" , taxId )
2023-03-21 10:58:54 +00:00
if IsHTMxRequest ( r ) {
w . WriteHeader ( http . StatusOK )
} else {
http . Redirect ( w , r , companyURI ( mustGetCompany ( r ) , "/tax-details" ) , http . StatusSeeOther )
}
2023-02-03 11:30:56 +00:00
}
func HandleAddCompanyTax ( w http . ResponseWriter , r * http . Request , _ httprouter . Params ) {
locale := getLocale ( r )
2023-02-28 11:02:27 +00:00
conn := getConn ( r )
company := mustGetCompany ( r )
form := newTaxForm ( r . Context ( ) , conn , company , locale )
2023-02-03 11:30:56 +00:00
if err := form . Parse ( r ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
if err := verifyCsrfTokenValid ( r ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusForbidden )
return
}
if ! form . Validate ( ) {
2023-03-21 10:58:54 +00:00
if ! IsHTMxRequest ( r ) {
w . WriteHeader ( http . StatusUnprocessableEntity )
}
2023-02-03 11:30:56 +00:00
mustRenderTaxForm ( w , r , form )
return
}
2023-02-28 11:02:27 +00:00
conn . MustExec ( r . Context ( ) , "insert into tax (company_id, tax_class_id, name, rate) values ($1, $2, $3, $4 / 100::decimal)" , company . Id , form . Class , form . Name , form . Rate . Integer ( ) )
2023-03-21 10:58:54 +00:00
if IsHTMxRequest ( r ) {
mustRenderTaxForm ( w , r , newTaxForm ( r . Context ( ) , conn , company , locale ) )
} else {
http . Redirect ( w , r , companyURI ( company , "/tax-details" ) , http . StatusSeeOther )
}
2023-01-28 13:18:58 +00:00
}
2023-03-03 15:49:06 +00:00
type paymentMethodForm struct {
locale * Locale
Name * InputField
Instructions * InputField
}
func newPaymentMethodForm ( locale * Locale ) * paymentMethodForm {
return & paymentMethodForm {
locale : locale ,
Name : & InputField {
Name : "method_name" ,
Label : pgettext ( "input" , "Payment method name" , locale ) ,
Type : "text" ,
Required : true ,
} ,
Instructions : & InputField {
Name : "method_instructions" ,
Label : pgettext ( "input" , "Instructions" , locale ) ,
Type : "textarea" ,
Required : true ,
} ,
}
}
func ( form * paymentMethodForm ) Parse ( r * http . Request ) error {
if err := r . ParseForm ( ) ; err != nil {
return err
}
form . Name . FillValue ( r )
form . Instructions . FillValue ( r )
return nil
}
func ( form * paymentMethodForm ) Validate ( ) bool {
validator := newFormValidator ( )
validator . CheckRequiredInput ( form . Name , gettext ( "Payment method name can not be empty." , form . locale ) )
validator . CheckRequiredInput ( form . Instructions , gettext ( "Payment instructions can not be empty." , form . locale ) )
return validator . AllOK ( )
}
func HandleDeletePaymentMethod ( w http . ResponseWriter , r * http . Request , params httprouter . Params ) {
paymentMethodId , err := strconv . Atoi ( params [ 0 ] . Value )
if err != nil {
http . NotFound ( w , r )
return
}
if err := verifyCsrfTokenValid ( r ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusForbidden )
return
}
conn := getConn ( r )
conn . MustExec ( r . Context ( ) , "delete from payment_method where payment_method_id = $1" , paymentMethodId )
2023-03-21 10:58:54 +00:00
if IsHTMxRequest ( r ) {
w . WriteHeader ( http . StatusOK )
} else {
http . Redirect ( w , r , companyURI ( mustGetCompany ( r ) , "/tax-details" ) , http . StatusSeeOther )
}
2023-03-03 15:49:06 +00:00
}
func HandleAddPaymentMethod ( w http . ResponseWriter , r * http . Request , _ httprouter . Params ) {
locale := getLocale ( r )
conn := getConn ( r )
company := mustGetCompany ( r )
form := newPaymentMethodForm ( locale )
if err := form . Parse ( r ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
if err := verifyCsrfTokenValid ( r ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusForbidden )
return
}
if ! form . Validate ( ) {
2023-03-21 10:58:54 +00:00
if ! IsHTMxRequest ( r ) {
w . WriteHeader ( http . StatusUnprocessableEntity )
}
2023-03-03 15:49:06 +00:00
mustRenderPaymentMethodForm ( w , r , form )
return
}
conn . MustExec ( r . Context ( ) , "insert into payment_method (company_id, name, instructions) values ($1, $2, $3)" , company . Id , form . Name , form . Instructions )
2023-03-21 10:58:54 +00:00
if IsHTMxRequest ( r ) {
mustRenderPaymentMethodForm ( w , r , newPaymentMethodForm ( locale ) )
} else {
http . Redirect ( w , r , companyURI ( company , "/tax-details" ) , http . StatusSeeOther )
}
2023-03-03 15:49:06 +00:00
}
2023-11-06 12:52:34 +00:00
func GetCompanySwitcher ( w http . ResponseWriter , r * http . Request , _ httprouter . Params ) {
page := & CompanySwitchPage {
Companies : mustCollectUserCompanies ( r . Context ( ) , getConn ( r ) ) ,
}
page . MustRender ( w , r )
}
type CompanySwitchPage struct {
Companies [ ] * UserCompany
}
type UserCompany struct {
Name string
Slug string
}
func ( page * CompanySwitchPage ) MustRender ( w http . ResponseWriter , r * http . Request ) {
mustRenderModalTemplate ( w , r , "switch-company.gohtml" , page )
}
func mustCollectUserCompanies ( ctx context . Context , conn * Conn ) [ ] * UserCompany {
rows , err := conn . Query ( ctx , "select business_name::text, slug::text from company order by business_name" )
if err != nil {
panic ( err )
}
defer rows . Close ( )
var companies [ ] * UserCompany
for rows . Next ( ) {
company := & UserCompany { }
err = rows . Scan ( & company . Name , & company . Slug )
if err != nil {
panic ( err )
}
companies = append ( companies , company )
}
if rows . Err ( ) != nil {
panic ( rows . Err ( ) )
}
return companies
}