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:
parent
1855122d16
commit
083d14e324
|
@ -9,7 +9,7 @@ set search_path to numerus, public;
|
||||||
create table invoice_number_counter (
|
create table invoice_number_counter (
|
||||||
company_id integer not null,
|
company_id integer not null,
|
||||||
year integer not null constraint year_always_positive check(year > 0),
|
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)
|
primary key (company_id, year)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -87,6 +88,7 @@ type taxDetailsForm struct {
|
||||||
*contactForm
|
*contactForm
|
||||||
Currency *SelectField
|
Currency *SelectField
|
||||||
InvoiceNumberFormat *InputField
|
InvoiceNumberFormat *InputField
|
||||||
|
NextInvoiceNumber *InputField
|
||||||
LegalDisclaimer *InputField
|
LegalDisclaimer *InputField
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +108,15 @@ func newTaxDetailsForm(ctx context.Context, conn *Conn, locale *Locale) *taxDeta
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Required: true,
|
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{
|
LegalDisclaimer: &InputField{
|
||||||
Name: "legal_disclaimer",
|
Name: "legal_disclaimer",
|
||||||
Label: pgettext("input", "Legal disclaimer", locale),
|
Label: pgettext("input", "Legal disclaimer", locale),
|
||||||
|
@ -120,6 +131,7 @@ func (form *taxDetailsForm) Parse(r *http.Request) error {
|
||||||
}
|
}
|
||||||
form.Currency.FillValue(r)
|
form.Currency.FillValue(r)
|
||||||
form.InvoiceNumberFormat.FillValue(r)
|
form.InvoiceNumberFormat.FillValue(r)
|
||||||
|
form.NextInvoiceNumber.FillValue(r)
|
||||||
form.LegalDisclaimer.FillValue(r)
|
form.LegalDisclaimer.FillValue(r)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -128,11 +140,51 @@ func (form *taxDetailsForm) Validate(ctx context.Context, conn *Conn) bool {
|
||||||
validator := newFormValidator()
|
validator := newFormValidator()
|
||||||
validator.CheckValidSelectOption(form.Currency, gettext("Selected currency is not valid.", form.locale))
|
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.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()
|
return form.contactForm.Validate(ctx, conn) && validator.AllOK()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (form *taxDetailsForm) mustFillFromDatabase(ctx context.Context, conn *Conn, company *Company) *taxDetailsForm {
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -183,6 +235,7 @@ func HandleCompanyTaxDetailsForm(w http.ResponseWriter, r *http.Request, _ httpr
|
||||||
}
|
}
|
||||||
company := mustGetCompany(r)
|
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 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) {
|
if IsHTMxRequest(r) {
|
||||||
w.Header().Set(HxTrigger, "closeModal")
|
w.Header().Set(HxTrigger, "closeModal")
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
|
@ -117,12 +117,11 @@ select throws_ok(
|
||||||
reset role;
|
reset role;
|
||||||
|
|
||||||
|
|
||||||
select throws_ok( $$
|
select lives_ok( $$
|
||||||
insert into invoice_number_counter (company_id, year, currval)
|
insert into invoice_number_counter (company_id, year, currval)
|
||||||
values (2, 2008, 0)
|
values (2, 2008, 0)
|
||||||
$$,
|
$$,
|
||||||
'23514', 'new row for relation "invoice_number_counter" violates check constraint "counter_always_positive"',
|
'Should allow starting a counter from zero'
|
||||||
'Should not allow starting a counter from zero'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
select throws_ok( $$
|
select throws_ok( $$
|
||||||
|
|
|
@ -560,6 +560,21 @@ main > nav {
|
||||||
margin-bottom: 4rem;
|
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 */
|
/* Invoice */
|
||||||
|
|
||||||
.new-invoice-product input {
|
.new-invoice-product input {
|
||||||
|
|
|
@ -37,10 +37,11 @@
|
||||||
{{ template "select-field" .Currency }}
|
{{ template "select-field" .Currency }}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset id="invoicing">
|
||||||
<legend>{{( pgettext "Invoicing" "title" )}}</legend>
|
<legend>{{( pgettext "Invoicing" "title" )}}</legend>
|
||||||
|
|
||||||
{{ template "input-field" .InvoiceNumberFormat }}
|
{{ template "input-field" .InvoiceNumberFormat }}
|
||||||
|
{{ template "input-field" .NextInvoiceNumber }}
|
||||||
{{ template "input-field" .LegalDisclaimer }}
|
{{ template "input-field" .LegalDisclaimer }}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
|
Loading…
Reference in New Issue