Add the payments section
This actually should be the “payments and receivables” section, however
this is quite a mouthful; a “receivable” is a payment made **to** you,
therefore “payments” is ok.
In fact, there is still no receivables in there, as they should be in
a separate relation, to constraint them to invoices instead of expenses.
It will be done in a separate commit.
Since this section will be, in a sense, sort of simplified accounting,
i needed to introduce the “payment account” concept. There is no way,
yet, for users to add them, because i have to revamp the “tax details”
section, but this commit started to grow too big already.
The same reasoning for the attachment payment slips as PDF to payment:
something i have to add, but not yet in this commit.
2024-08-10 02:34:07 +00:00
package pkg
import (
"context"
"github.com/jackc/pgtype"
"github.com/julienschmidt/httprouter"
"html/template"
"net/http"
"time"
)
const (
ExpirationDateFormat = "01/06"
AccountTypeBank = "bank"
AccountTypeCard = "card"
AccountTypeCash = "cash"
AccountTypeOther = "other"
)
func servePaymentAccountIndex ( w http . ResponseWriter , r * http . Request , _ httprouter . Params ) {
conn := getConn ( r )
2024-08-15 00:59:46 +00:00
company := mustGetCompany ( r )
Add the payments section
This actually should be the “payments and receivables” section, however
this is quite a mouthful; a “receivable” is a payment made **to** you,
therefore “payments” is ok.
In fact, there is still no receivables in there, as they should be in
a separate relation, to constraint them to invoices instead of expenses.
It will be done in a separate commit.
Since this section will be, in a sense, sort of simplified accounting,
i needed to introduce the “payment account” concept. There is no way,
yet, for users to add them, because i have to revamp the “tax details”
section, but this commit started to grow too big already.
The same reasoning for the attachment payment slips as PDF to payment:
something i have to add, but not yet in this commit.
2024-08-10 02:34:07 +00:00
locale := getLocale ( r )
2024-08-15 00:59:46 +00:00
page := NewPaymentAccountIndexPage ( r . Context ( ) , conn , company , locale )
Add the payments section
This actually should be the “payments and receivables” section, however
this is quite a mouthful; a “receivable” is a payment made **to** you,
therefore “payments” is ok.
In fact, there is still no receivables in there, as they should be in
a separate relation, to constraint them to invoices instead of expenses.
It will be done in a separate commit.
Since this section will be, in a sense, sort of simplified accounting,
i needed to introduce the “payment account” concept. There is no way,
yet, for users to add them, because i have to revamp the “tax details”
section, but this commit started to grow too big already.
The same reasoning for the attachment payment slips as PDF to payment:
something i have to add, but not yet in this commit.
2024-08-10 02:34:07 +00:00
page . MustRender ( w , r )
}
type PaymentAccountIndexPage struct {
Accounts [ ] * PaymentAccountEntry
}
2024-08-15 00:59:46 +00:00
func NewPaymentAccountIndexPage ( ctx context . Context , conn * Conn , company * Company , locale * Locale ) * PaymentAccountIndexPage {
Add the payments section
This actually should be the “payments and receivables” section, however
this is quite a mouthful; a “receivable” is a payment made **to** you,
therefore “payments” is ok.
In fact, there is still no receivables in there, as they should be in
a separate relation, to constraint them to invoices instead of expenses.
It will be done in a separate commit.
Since this section will be, in a sense, sort of simplified accounting,
i needed to introduce the “payment account” concept. There is no way,
yet, for users to add them, because i have to revamp the “tax details”
section, but this commit started to grow too big already.
The same reasoning for the attachment payment slips as PDF to payment:
something i have to add, but not yet in this commit.
2024-08-10 02:34:07 +00:00
return & PaymentAccountIndexPage {
2024-08-15 00:59:46 +00:00
Accounts : mustCollectPaymentAccountEntries ( ctx , conn , company , locale ) ,
Add the payments section
This actually should be the “payments and receivables” section, however
this is quite a mouthful; a “receivable” is a payment made **to** you,
therefore “payments” is ok.
In fact, there is still no receivables in there, as they should be in
a separate relation, to constraint them to invoices instead of expenses.
It will be done in a separate commit.
Since this section will be, in a sense, sort of simplified accounting,
i needed to introduce the “payment account” concept. There is no way,
yet, for users to add them, because i have to revamp the “tax details”
section, but this commit started to grow too big already.
The same reasoning for the attachment payment slips as PDF to payment:
something i have to add, but not yet in this commit.
2024-08-10 02:34:07 +00:00
}
}
func ( page * PaymentAccountIndexPage ) MustRender ( w http . ResponseWriter , r * http . Request ) {
mustRenderMainTemplate ( w , r , "payments/accounts/index.gohtml" , page )
}
type PaymentAccountEntry struct {
ID int
Slug string
Type string
TypeLabel string
Name string
IBAN string
LastFourDigits string
ExpirationDate string
}
2024-08-15 00:59:46 +00:00
func mustCollectPaymentAccountEntries ( ctx context . Context , conn * Conn , company * Company , locale * Locale ) [ ] * PaymentAccountEntry {
Add the payments section
This actually should be the “payments and receivables” section, however
this is quite a mouthful; a “receivable” is a payment made **to** you,
therefore “payments” is ok.
In fact, there is still no receivables in there, as they should be in
a separate relation, to constraint them to invoices instead of expenses.
It will be done in a separate commit.
Since this section will be, in a sense, sort of simplified accounting,
i needed to introduce the “payment account” concept. There is no way,
yet, for users to add them, because i have to revamp the “tax details”
section, but this commit started to grow too big already.
The same reasoning for the attachment payment slips as PDF to payment:
something i have to add, but not yet in this commit.
2024-08-10 02:34:07 +00:00
rows := conn . MustQuery ( ctx , `
select payment_account_id
, slug
, payment_account . payment_account_type
, coalesce ( i18n . name , payment_account_type . name )
, payment_account . name
, coalesce ( iban : : text , ' ' ) as iban
, coalesce ( last_four_digits , ' ' ) as last_four_digits
, expiration_date
from payment_account
left join payment_account_bank using ( payment_account_id , payment_account_type )
left join payment_account_card using ( payment_account_id , payment_account_type )
join payment_account_type using ( payment_account_type )
left join payment_account_type_i18n as i18n on payment_account_type . payment_account_type = i18n . payment_account_type and i18n . lang_tag = $ 1
2024-08-15 00:59:46 +00:00
where company_id = $ 2
Add the payments section
This actually should be the “payments and receivables” section, however
this is quite a mouthful; a “receivable” is a payment made **to** you,
therefore “payments” is ok.
In fact, there is still no receivables in there, as they should be in
a separate relation, to constraint them to invoices instead of expenses.
It will be done in a separate commit.
Since this section will be, in a sense, sort of simplified accounting,
i needed to introduce the “payment account” concept. There is no way,
yet, for users to add them, because i have to revamp the “tax details”
section, but this commit started to grow too big already.
The same reasoning for the attachment payment slips as PDF to payment:
something i have to add, but not yet in this commit.
2024-08-10 02:34:07 +00:00
order by payment_account_id
2024-08-15 00:59:46 +00:00
` , locale . Language . String ( ) , company . Id )
Add the payments section
This actually should be the “payments and receivables” section, however
this is quite a mouthful; a “receivable” is a payment made **to** you,
therefore “payments” is ok.
In fact, there is still no receivables in there, as they should be in
a separate relation, to constraint them to invoices instead of expenses.
It will be done in a separate commit.
Since this section will be, in a sense, sort of simplified accounting,
i needed to introduce the “payment account” concept. There is no way,
yet, for users to add them, because i have to revamp the “tax details”
section, but this commit started to grow too big already.
The same reasoning for the attachment payment slips as PDF to payment:
something i have to add, but not yet in this commit.
2024-08-10 02:34:07 +00:00
defer rows . Close ( )
var entries [ ] * PaymentAccountEntry
for rows . Next ( ) {
entry := & PaymentAccountEntry { }
var expirationDate pgtype . Date
if err := rows . Scan ( & entry . ID , & entry . Slug , & entry . Type , & entry . TypeLabel , & entry . Name , & entry . IBAN , & entry . LastFourDigits , & expirationDate ) ; err != nil {
panic ( err )
}
if expirationDate . Status == pgtype . Present {
entry . ExpirationDate = expirationDate . Time . Format ( ExpirationDateFormat )
}
entries = append ( entries , entry )
}
if rows . Err ( ) != nil {
panic ( rows . Err ( ) )
}
return entries
}
func servePaymentAccountForm ( w http . ResponseWriter , r * http . Request , params httprouter . Params ) {
locale := getLocale ( r )
conn := getConn ( r )
company := mustGetCompany ( r )
form := newPaymentAccountForm ( r . Context ( ) , conn , locale , company )
slug := params [ 0 ] . Value
if slug == "new" {
form . MustRender ( w , r )
return
}
if ! ValidUuid ( slug ) {
http . NotFound ( w , r )
return
}
if ! form . MustFillFromDatabase ( r . Context ( ) , conn , slug ) {
http . NotFound ( w , r )
return
}
form . MustRender ( w , r )
}
type PaymentAccountForm struct {
locale * Locale
company * Company
Slug string
Type * RadioField
Name * InputField
IBAN * InputField
LastFourDigits * InputField
ExpirationMonthYear * InputField
}
func newPaymentAccountForm ( ctx context . Context , conn * Conn , locale * Locale , company * Company ) * PaymentAccountForm {
return & PaymentAccountForm {
locale : locale ,
company : company ,
Type : & RadioField {
Name : "payment_account_type" ,
Label : pgettext ( "input" , "Type" , locale ) ,
Required : true ,
Options : MustGetRadioOptions ( ctx , conn , "select payment_account_type, i18n.name from payment_account_type join payment_account_type_i18n as i18n using (payment_account_type) where i18n.lang_tag = $1 order by payment_account_type" , locale . Language . String ( ) ) ,
Attributes : [ ] template . HTMLAttr {
` x-model="type" ` ,
} ,
} ,
Name : & InputField {
Name : "name" ,
Label : pgettext ( "input" , "Name" , locale ) ,
Required : true ,
Type : "text" ,
} ,
IBAN : & InputField {
Name : "iban" ,
Label : pgettext ( "input" , "IBAN" , locale ) ,
Required : true ,
Type : "text" ,
} ,
LastFourDigits : & InputField {
Name : "last_four_digits" ,
Label : pgettext ( "input" , "Card’ s last four digits" , locale ) ,
Required : true ,
Type : "text" ,
Attributes : [ ] template . HTMLAttr {
` maxlength="4" ` ,
` minlength="4" ` ,
` pattern="[0-9] { 4}" ` ,
} ,
} ,
ExpirationMonthYear : & InputField {
Name : "expiration_date" ,
Label : pgettext ( "input" , "Expiration date" , locale ) ,
Required : true ,
Type : "text" ,
Attributes : [ ] template . HTMLAttr {
` maxlength="5" ` ,
` minlength="5" ` ,
` pattern="[0-9] { 2}/[0-9] { 2}" ` ,
} ,
} ,
}
}
func ( f * PaymentAccountForm ) MustRender ( w http . ResponseWriter , r * http . Request ) {
if f . Slug == "" {
mustRenderMainTemplate ( w , r , "payments/accounts/new.gohtml" , f )
} else {
mustRenderMainTemplate ( w , r , "payments/accounts/edit.gohtml" , f )
}
}
func ( f * PaymentAccountForm ) MustFillFromDatabase ( ctx context . Context , conn * Conn , slug string ) bool {
selectedType := f . Type . Selected
var expirationDate pgtype . Date
if notFoundErrorOrPanic ( conn . QueryRow ( ctx , `
select payment_account_type
, name
, coalesce ( iban : : text , ' ' ) as iban
, coalesce ( last_four_digits , ' ' ) as last_four_digits
, expiration_date
from payment_account
left join payment_account_bank using ( payment_account_id , payment_account_type )
left join payment_account_card using ( payment_account_id , payment_account_type )
where slug = $ 1
` , slug ) . Scan (
f . Type ,
f . Name ,
f . IBAN ,
f . LastFourDigits ,
& expirationDate ) ) {
f . Type . Selected = selectedType
return false
}
f . Slug = slug
if expirationDate . Status == pgtype . Present {
f . ExpirationMonthYear . Val = expirationDate . Time . Format ( ExpirationDateFormat )
} else {
f . ExpirationMonthYear . Val = ""
}
return true
}
func ( f * PaymentAccountForm ) Parse ( r * http . Request ) error {
if err := r . ParseForm ( ) ; err != nil {
return err
}
f . Type . FillValue ( r )
f . Name . FillValue ( r )
f . IBAN . FillValue ( r )
f . LastFourDigits . FillValue ( r )
f . ExpirationMonthYear . FillValue ( r )
return nil
}
func ( f * PaymentAccountForm ) Validate ( ctx context . Context , conn * Conn ) bool {
validator := newFormValidator ( )
if validator . CheckValidRadioOption ( f . Type , gettext ( "Selected payment account type is not valid." , f . locale ) ) {
switch f . Type . Selected {
case AccountTypeBank :
if validator . CheckRequiredInput ( f . IBAN , gettext ( "IBAN can not be empty." , f . locale ) ) {
validator . CheckValidIBANInput ( ctx , conn , f . IBAN , gettext ( "This value is not a valid IBAN." , f . locale ) )
}
case AccountTypeCard :
if validator . CheckRequiredInput ( f . LastFourDigits , gettext ( "Last four digits can not be empty." , f . locale ) ) {
if validator . CheckInputLength ( f . LastFourDigits , 4 , gettext ( "You must enter the card’ s last four digits" , f . locale ) ) {
validator . CheckValidInteger ( f . LastFourDigits , 0 , 9999 , gettext ( "Last four digits must be a number." , f . locale ) )
}
}
if validator . CheckRequiredInput ( f . ExpirationMonthYear , gettext ( "Expiration date can not be empty." , f . locale ) ) {
_ , err := time . Parse ( ExpirationDateFormat , f . ExpirationMonthYear . Val )
validator . checkInput ( f . ExpirationMonthYear , err == nil , gettext ( "Expiration date should be a valid date in format MM/YY (e.g., 08/24)." , f . locale ) )
}
}
}
validator . CheckRequiredInput ( f . Name , gettext ( "Payment account name can not be empty." , f . locale ) )
return validator . AllOK ( )
}
func ( f * PaymentAccountForm ) ExpirationDate ( ) ( time . Time , error ) {
date , err := time . Parse ( ExpirationDateFormat , f . ExpirationMonthYear . Val )
if err != nil {
return date , err
}
return date . AddDate ( 0 , 1 , - 1 ) , nil
}
func handleAddPaymentAccount ( w http . ResponseWriter , r * http . Request , _ httprouter . Params ) {
locale := getLocale ( r )
conn := getConn ( r )
company := mustGetCompany ( r )
form := newPaymentAccountForm ( r . Context ( ) , conn , locale , company )
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 ( r . Context ( ) , conn ) {
2024-08-27 09:07:39 +00:00
w . WriteHeader ( http . StatusUnprocessableEntity )
Add the payments section
This actually should be the “payments and receivables” section, however
this is quite a mouthful; a “receivable” is a payment made **to** you,
therefore “payments” is ok.
In fact, there is still no receivables in there, as they should be in
a separate relation, to constraint them to invoices instead of expenses.
It will be done in a separate commit.
Since this section will be, in a sense, sort of simplified accounting,
i needed to introduce the “payment account” concept. There is no way,
yet, for users to add them, because i have to revamp the “tax details”
section, but this commit started to grow too big already.
The same reasoning for the attachment payment slips as PDF to payment:
something i have to add, but not yet in this commit.
2024-08-10 02:34:07 +00:00
form . MustRender ( w , r )
return
}
switch form . Type . Selected {
case AccountTypeBank :
conn . MustExec ( r . Context ( ) , "select add_payment_account_bank($1, $2, $3)" , company . Id , form . Name , form . IBAN )
case AccountTypeCard :
date , err := form . ExpirationDate ( )
if err != nil {
panic ( err )
}
conn . MustExec ( r . Context ( ) , "select add_payment_account_card($1, $2, $3, $4)" , company . Id , form . Name , form . LastFourDigits , date )
case AccountTypeCash :
conn . MustExec ( r . Context ( ) , "select add_payment_account_cash($1, $2)" , company . Id , form . Name )
case AccountTypeOther :
conn . MustExec ( r . Context ( ) , "select add_payment_account_other($1, $2)" , company . Id , form . Name )
}
htmxRedirect ( w , r , companyURI ( company , "/payment-accounts" ) )
}
func handleEditPaymentAccount ( w http . ResponseWriter , r * http . Request , params httprouter . Params ) {
conn := getConn ( r )
locale := getLocale ( r )
company := mustGetCompany ( r )
form := newPaymentAccountForm ( r . Context ( ) , conn , locale , company )
form . Slug = params [ 0 ] . Value
if ! ValidUuid ( form . Slug ) {
http . NotFound ( w , r )
return
}
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 ( r . Context ( ) , conn ) {
2024-08-27 09:07:39 +00:00
w . WriteHeader ( http . StatusUnprocessableEntity )
Add the payments section
This actually should be the “payments and receivables” section, however
this is quite a mouthful; a “receivable” is a payment made **to** you,
therefore “payments” is ok.
In fact, there is still no receivables in there, as they should be in
a separate relation, to constraint them to invoices instead of expenses.
It will be done in a separate commit.
Since this section will be, in a sense, sort of simplified accounting,
i needed to introduce the “payment account” concept. There is no way,
yet, for users to add them, because i have to revamp the “tax details”
section, but this commit started to grow too big already.
The same reasoning for the attachment payment slips as PDF to payment:
something i have to add, but not yet in this commit.
2024-08-10 02:34:07 +00:00
form . MustRender ( w , r )
return
}
var found string
switch form . Type . Selected {
case AccountTypeBank :
found = conn . MustGetText ( r . Context ( ) , "" , "select edit_payment_account_bank($1, $2, $3)" , form . Slug , form . Name , form . IBAN )
case AccountTypeCard :
date , err := form . ExpirationDate ( )
if err != nil {
panic ( err )
}
found = conn . MustGetText ( r . Context ( ) , "" , "select edit_payment_account_card($1, $2, $3, $4)" , form . Slug , form . Name , form . LastFourDigits , date )
case AccountTypeCash :
found = conn . MustGetText ( r . Context ( ) , "" , "select edit_payment_account_cash($1, $2)" , form . Slug , form . Name )
case AccountTypeOther :
found = conn . MustGetText ( r . Context ( ) , "" , "select edit_payment_account_other($1, $2)" , form . Slug , form . Name )
}
if found == "" {
http . NotFound ( w , r )
return
}
htmxRedirect ( w , r , companyURI ( company , "/payment-accounts" ) )
}