Allow to change the current year’s invoice number counter

This is for new users that do not start using the application from the
beginning of the current fiscal year and, therefore, need to create
invoices starting from a specific number.

I had to change the constraint on the currval to allow zero, otherwise
it would not be possible to set 1 as the next number, because users
can also not delete the row.
This commit is contained in:
jordi fita mas 2023-05-31 20:01:00 +02:00
parent 1855122d16
commit 083d14e324
5 changed files with 74 additions and 6 deletions

View File

@ -9,7 +9,7 @@ set search_path to numerus, public;
create table invoice_number_counter (
company_id integer not null,
year integer not null constraint year_always_positive check(year > 0),
currval integer not null constraint counter_always_positive check(currval > 0),
currval integer not null constraint counter_zero_or_positive check(currval >= 0),
primary key (company_id, year)
);

View File

@ -5,6 +5,7 @@ import (
"errors"
"github.com/julienschmidt/httprouter"
"html/template"
"math"
"net/http"
"net/url"
"strconv"
@ -87,6 +88,7 @@ type taxDetailsForm struct {
*contactForm
Currency *SelectField
InvoiceNumberFormat *InputField
NextInvoiceNumber *InputField
LegalDisclaimer *InputField
}
@ -106,6 +108,15 @@ func newTaxDetailsForm(ctx context.Context, conn *Conn, locale *Locale) *taxDeta
Type: "text",
Required: true,
},
NextInvoiceNumber: &InputField{
Name: "next_invoice_number",
Label: pgettext("input", "Next invoice number", locale),
Type: "number",
Required: true,
Attributes: []template.HTMLAttr{
"min=1",
},
},
LegalDisclaimer: &InputField{
Name: "legal_disclaimer",
Label: pgettext("input", "Legal disclaimer", locale),
@ -120,6 +131,7 @@ func (form *taxDetailsForm) Parse(r *http.Request) error {
}
form.Currency.FillValue(r)
form.InvoiceNumberFormat.FillValue(r)
form.NextInvoiceNumber.FillValue(r)
form.LegalDisclaimer.FillValue(r)
return nil
}
@ -128,11 +140,51 @@ func (form *taxDetailsForm) Validate(ctx context.Context, conn *Conn) bool {
validator := newFormValidator()
validator.CheckValidSelectOption(form.Currency, gettext("Selected currency is not valid.", form.locale))
validator.CheckRequiredInput(form.InvoiceNumberFormat, gettext("Invoice number format can not be empty.", form.locale))
validator.CheckValidInteger(form.NextInvoiceNumber, 1, math.MaxInt32, gettext("Next invoice number must be a number greater than zero.", form.locale))
return form.contactForm.Validate(ctx, conn) && validator.AllOK()
}
func (form *taxDetailsForm) mustFillFromDatabase(ctx context.Context, conn *Conn, company *Company) *taxDetailsForm {
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, legal_disclaimer from company where 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, form.LegalDisclaimer)
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
, legal_disclaimer
, coalesce(invoice_number_counter.currval, 0) + 1
from company
left join invoice_number_counter
on invoice_number_counter.company_id = company.company_id
and year = date_part('year', current_date)
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,
form.LegalDisclaimer,
form.NextInvoiceNumber,
)
if err != nil {
panic(err)
}
if err != nil {
panic(err)
}
@ -183,6 +235,7 @@ func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httpr
}
company := mustGetCompany(r)
conn.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, legal_disclaimer = $14 where company_id = $15", 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.LegalDisclaimer, company.Id)
conn.MustExec(r.Context(), "update invoice_number_counter set currval = $1 where company_id = $2 and year = date_part('year', current_date)", form.NextInvoiceNumber.Integer()-1, company.Id)
if IsHTMxRequest(r) {
w.Header().Set(HxTrigger, "closeModal")
w.WriteHeader(http.StatusNoContent)

View File

@ -117,12 +117,11 @@ select throws_ok(
reset role;
select throws_ok( $$
select lives_ok( $$
insert into invoice_number_counter (company_id, year, currval)
values (2, 2008, 0)
$$,
'23514', 'new row for relation "invoice_number_counter" violates check constraint "counter_always_positive"',
'Should not allow starting a counter from zero'
'Should allow starting a counter from zero'
);
select throws_ok( $$

View File

@ -560,6 +560,21 @@ main > nav {
margin-bottom: 4rem;
}
/* Tax Details */
#invoicing {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
#invoicing .input:last-child {
grid-column-start: 1;
grid-column-end: 3;
}
#invoicing .input:last-child textarea {
min-height: 20ex;
}
/* Invoice */
.new-invoice-product input {

View File

@ -37,10 +37,11 @@
{{ template "select-field" .Currency }}
</fieldset>
<fieldset>
<fieldset id="invoicing">
<legend>{{( pgettext "Invoicing" "title" )}}</legend>
{{ template "input-field" .InvoiceNumberFormat }}
{{ template "input-field" .NextInvoiceNumber }}
{{ template "input-field" .LegalDisclaimer }}
</fieldset>
</form>