Compare commits

..

No commits in common. "b48a9740865397b61139725d25af86a7ee94d194" and "1164210d849a01879726d4eec5f15270f2da768f" have entirely different histories.

36 changed files with 103 additions and 693 deletions

View File

@ -8,27 +8,24 @@
-- requires: parse_price -- requires: parse_price
-- requires: tax -- requires: tax
-- requires: tag_name -- requires: tag_name
-- requires: expense_status
-- requires: expense_expense_status
begin; begin;
set search_path to numerus, public; set search_path to numerus, public;
create or replace function add_expense(company integer, status text, invoice_date date, contact_id integer, invoice_number text, amount text, taxes integer[], tags tag_name[]) returns uuid as create or replace function add_expense(company integer, invoice_date date, contact_id integer, invoice_number text, amount text, taxes integer[], tags tag_name[]) returns uuid as
$$ $$
declare declare
eid integer; eid integer;
eslug uuid; eslug uuid;
begin begin
insert into expense (company_id, contact_id, invoice_number, invoice_date, amount, currency_code, expense_status, tags) insert into expense (company_id, contact_id, invoice_number, invoice_date, amount, currency_code, tags)
select company_id select company_id
, contact_id , contact_id
, invoice_number , invoice_number
, invoice_date , invoice_date
, parse_price(amount, currency.decimal_digits) , parse_price(amount, currency.decimal_digits)
, currency_code , currency_code
, status
, tags , tags
from company from company
join currency using (currency_code) join currency using (currency_code)
@ -46,10 +43,8 @@ end;
$$ $$
language plpgsql; language plpgsql;
revoke execute on function add_expense(integer, text, date, integer, text, text, integer[], tag_name[]) from public; revoke execute on function add_expense(integer, date, integer, text, text, integer[], tag_name[]) from public;
grant execute on function add_expense(integer, text, date, integer, text, text, integer[], tag_name[]) to invoicer; grant execute on function add_expense(integer, date, integer, text, text, integer[], tag_name[]) to invoicer;
grant execute on function add_expense(integer, text, date, integer, text, text, integer[], tag_name[]) to admin; grant execute on function add_expense(integer, date, integer, text, text, integer[], tag_name[]) to admin;
drop function if exists add_expense(integer, date, integer, text, text, integer[], tag_name[]);
commit; commit;

View File

@ -1,50 +0,0 @@
-- Deploy numerus:add_expense to pg
-- requires: schema_numerus
-- requires: expense
-- requires: expense_tax
-- requires: tax
-- requires: company
-- requires: currency
-- requires: parse_price
-- requires: tax
-- requires: tag_name
begin;
set search_path to numerus, public;
create or replace function add_expense(company integer, invoice_date date, contact_id integer, invoice_number text, amount text, taxes integer[], tags tag_name[]) returns uuid as
$$
declare
eid integer;
eslug uuid;
begin
insert into expense (company_id, contact_id, invoice_number, invoice_date, amount, currency_code, tags)
select company_id
, contact_id
, invoice_number
, invoice_date
, parse_price(amount, currency.decimal_digits)
, currency_code
, tags
from company
join currency using (currency_code)
where company.company_id = add_expense.company
returning expense_id, slug
into eid, eslug;
insert into expense_tax (expense_id, tax_id, tax_rate)
select eid, tax_id, tax.rate
from tax
join unnest(taxes) as etax(tax_id) using (tax_id);
return eslug;
end;
$$
language plpgsql;
revoke execute on function add_expense(integer, date, integer, text, text, integer[], tag_name[]) from public;
grant execute on function add_expense(integer, date, integer, text, text, integer[], tag_name[]) to invoicer;
grant execute on function add_expense(integer, date, integer, text, text, integer[], tag_name[]) to admin;
commit;

View File

@ -1,22 +0,0 @@
-- Deploy numerus:available_expense_status to pg
-- requires: schema_numerus
-- requires: expense_status
-- requires: expense_status_i18n
begin;
set search_path to numerus;
insert into expense_status (expense_status, name)
values ('pending', 'Pending')
, ('paid', 'Paid')
;
insert into expense_status_i18n (expense_status, lang_tag, name)
values ('pending', 'ca', 'Pendent')
, ('paid', 'ca', 'Pagada')
, ('pending', 'es', 'Pendiente')
, ('paid', 'es', 'Pagada')
;
commit;

View File

@ -5,14 +5,12 @@
-- requires: parse_price -- requires: parse_price
-- requires: tax -- requires: tax
-- requires: tag_name -- requires: tag_name
-- requires: expense_status
-- requires: expense_expense_status
begin; begin;
set search_path to numerus, public; set search_path to numerus, public;
create or replace function edit_expense(expense_slug uuid, status text, invoice_date date, contact_id integer, invoice_number text, amount text, taxes integer[], tags tag_name[]) returns uuid as create or replace function edit_expense(expense_slug uuid, invoice_date date, contact_id integer, invoice_number text, amount text, taxes integer[], tags tag_name[]) returns uuid as
$$ $$
declare declare
eid integer; eid integer;
@ -22,7 +20,6 @@ begin
, contact_id = edit_expense.contact_id , contact_id = edit_expense.contact_id
, invoice_number = edit_expense.invoice_number , invoice_number = edit_expense.invoice_number
, amount = parse_price(edit_expense.amount, decimal_digits) , amount = parse_price(edit_expense.amount, decimal_digits)
, expense_status = status
, tags = edit_expense.tags , tags = edit_expense.tags
from currency from currency
where slug = expense_slug where slug = expense_slug
@ -46,8 +43,8 @@ end;
$$ $$
language plpgsql; language plpgsql;
revoke execute on function edit_expense(uuid, text, date, integer, text, text, integer[], tag_name[]) from public; revoke execute on function edit_expense(uuid, date, integer, text, text, integer[], tag_name[]) from public;
grant execute on function edit_expense(uuid, text, date, integer, text, text, integer[], tag_name[]) to invoicer; grant execute on function edit_expense(uuid, date, integer, text, text, integer[], tag_name[]) to invoicer;
grant execute on function edit_expense(uuid, text, date, integer, text, text, integer[], tag_name[]) to admin; grant execute on function edit_expense(uuid, date, integer, text, text, integer[], tag_name[]) to admin;
commit; commit;

View File

@ -1,50 +0,0 @@
-- Deploy numerus:edit_expense to pg
-- requires: schema_numerus
-- requires: expense
-- requires: currency
-- requires: parse_price
-- requires: tax
-- requires: tag_name
begin;
set search_path to numerus, public;
create or replace function edit_expense(expense_slug uuid, invoice_date date, contact_id integer, invoice_number text, amount text, taxes integer[], tags tag_name[]) returns uuid as
$$
declare
eid integer;
begin
update expense
set invoice_date = edit_expense.invoice_date
, contact_id = edit_expense.contact_id
, invoice_number = edit_expense.invoice_number
, amount = parse_price(edit_expense.amount, decimal_digits)
, tags = edit_expense.tags
from currency
where slug = expense_slug
and currency.currency_code = expense.currency_code
returning expense_id
into eid;
if eid is null then
return null;
end if;
delete from expense_tax where expense_id = eid;
insert into expense_tax (expense_id, tax_id, tax_rate)
select eid, tax_id, tax.rate
from tax
join unnest(taxes) as etax(tax_id) using (tax_id);
return expense_slug;
end;
$$
language plpgsql;
revoke execute on function edit_expense(uuid, date, integer, text, text, integer[], tag_name[]) from public;
grant execute on function edit_expense(uuid, date, integer, text, text, integer[], tag_name[]) to invoicer;
grant execute on function edit_expense(uuid, date, integer, text, text, integer[], tag_name[]) to admin;
commit;

View File

@ -1,12 +0,0 @@
-- Deploy numerus:expense_expense_status to pg
-- requires: expense
begin;
set search_path to numerus, public;
alter table expense
add column expense_status text not null default 'pending' references expense_status
;
commit;

View File

@ -1,17 +0,0 @@
-- Deploy numerus:expense_status to pg
-- requires: schema_numerus
-- requires: roles
begin;
set search_path to numerus, public;
create table expense_status (
expense_status text primary key,
name text not null
);
grant select on table expense_status to invoicer;
grant select on table expense_status to admin;
commit;

View File

@ -1,21 +0,0 @@
-- Deploy numerus:expense_status_i18n to pg
-- requires: schema_numerus
-- requires: roles
-- requires: expense_status
-- requires: language
begin;
set search_path to numerus, public;
create table expense_status_i18n (
expense_status text not null references expense_status,
lang_tag text not null references language,
name text not null,
primary key (expense_status, lang_tag)
);
grant select on table expense_status_i18n to invoicer;
grant select on table expense_status_i18n to admin;
commit;

View File

@ -20,15 +20,12 @@ type ExpenseEntry struct {
InvoicerName string InvoicerName string
OriginalFileName string OriginalFileName string
Tags []string Tags []string
Status string
StatusLabel string
} }
type expensesIndexPage struct { type expensesIndexPage struct {
Expenses []*ExpenseEntry Expenses []*ExpenseEntry
TotalAmount string TotalAmount string
Filters *expenseFilterForm Filters *expenseFilterForm
ExpenseStatuses map[string]string
} }
func IndexExpenses(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func IndexExpenses(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@ -41,16 +38,15 @@ func IndexExpenses(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
return return
} }
page := &expensesIndexPage{ page := &expensesIndexPage{
Expenses: mustCollectExpenseEntries(r.Context(), conn, locale, filters), Expenses: mustCollectExpenseEntries(r.Context(), conn, filters),
TotalAmount: mustComputeExpensesTotalAmount(r.Context(), conn, filters), TotalAmount: mustComputeExpensesTotalAmount(r.Context(), conn, filters),
ExpenseStatuses: mustCollectExpenseStatuses(r.Context(), conn, locale),
Filters: filters, Filters: filters,
} }
mustRenderMainTemplate(w, r, "expenses/index.gohtml", page) mustRenderMainTemplate(w, r, "expenses/index.gohtml", page)
} }
func mustCollectExpenseEntries(ctx context.Context, conn *Conn, locale *Locale, filters *expenseFilterForm) []*ExpenseEntry { func mustCollectExpenseEntries(ctx context.Context, conn *Conn, filters *expenseFilterForm) []*ExpenseEntry {
where, args := filters.BuildQuery([]interface{}{locale.Language.String()}) where, args := filters.BuildQuery(nil)
rows := conn.MustQuery(ctx, fmt.Sprintf(` rows := conn.MustQuery(ctx, fmt.Sprintf(`
select expense.slug select expense.slug
, invoice_date , invoice_date
@ -59,12 +55,9 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, locale *Locale,
, contact.name , contact.name
, coalesce(attachment.original_filename, '') , coalesce(attachment.original_filename, '')
, expense.tags , expense.tags
, expense.expense_status
, esi18n.name
from expense from expense
left join expense_attachment as attachment using (expense_id) left join expense_attachment as attachment using (expense_id)
join contact using (contact_id) join contact using (contact_id)
join expense_status_i18n esi18n on expense.expense_status = esi18n.expense_status and esi18n.lang_tag = $1
join currency using (currency_code) join currency using (currency_code)
where (%s) where (%s)
order by invoice_date order by invoice_date
@ -74,7 +67,7 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, locale *Locale,
var entries []*ExpenseEntry var entries []*ExpenseEntry
for rows.Next() { for rows.Next() {
entry := &ExpenseEntry{} entry := &ExpenseEntry{}
if err := rows.Scan(&entry.Slug, &entry.InvoiceDate, &entry.InvoiceNumber, &entry.Amount, &entry.InvoicerName, &entry.OriginalFileName, &entry.Tags, &entry.Status, &entry.StatusLabel); err != nil { if err := rows.Scan(&entry.Slug, &entry.InvoiceDate, &entry.InvoiceNumber, &entry.Amount, &entry.InvoicerName, &entry.OriginalFileName, &entry.Tags); err != nil {
panic(err) panic(err)
} }
entries = append(entries, entry) entries = append(entries, entry)
@ -86,31 +79,6 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, locale *Locale,
return entries return entries
} }
func mustCollectExpenseStatuses(ctx context.Context, conn *Conn, locale *Locale) map[string]string {
rows := conn.MustQuery(ctx, `
select expense_status.expense_status
, esi18n.name
from expense_status
join expense_status_i18n esi18n using(expense_status)
where esi18n.lang_tag = $1
order by expense_status`, locale.Language.String())
defer rows.Close()
statuses := map[string]string{}
for rows.Next() {
var key, name string
if err := rows.Scan(&key, &name); err != nil {
panic(err)
}
statuses[key] = name
}
if rows.Err() != nil {
panic(rows.Err())
}
return statuses
}
func mustComputeExpensesTotalAmount(ctx context.Context, conn *Conn, filters *expenseFilterForm) string { func mustComputeExpensesTotalAmount(ctx context.Context, conn *Conn, filters *expenseFilterForm) string {
where, args := filters.BuildQuery(nil) where, args := filters.BuildQuery(nil)
return conn.MustGetText(ctx, "0", fmt.Sprintf(` return conn.MustGetText(ctx, "0", fmt.Sprintf(`
@ -170,7 +138,6 @@ type expenseForm struct {
Tax *SelectField Tax *SelectField
Amount *InputField Amount *InputField
File *FileField File *FileField
ExpenseStatus *SelectField
Tags *TagsField Tags *TagsField
} }
@ -216,13 +183,6 @@ func newExpenseForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
Label: pgettext("input", "File", locale), Label: pgettext("input", "File", locale),
MaxSize: 1 << 20, MaxSize: 1 << 20,
}, },
ExpenseStatus: &SelectField{
Name: "expense_status",
Required: true,
Label: pgettext("input", "Expense Status", locale),
Selected: []string{"pending"},
Options: mustGetExpenseStatusOptions(ctx, conn, locale),
},
Tags: &TagsField{ Tags: &TagsField{
Name: "tags", Name: "tags",
Label: pgettext("input", "Tags", locale), Label: pgettext("input", "Tags", locale),
@ -230,16 +190,6 @@ func newExpenseForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
} }
} }
func mustGetExpenseStatusOptions(ctx context.Context, conn *Conn, locale *Locale) []*SelectOption {
return MustGetOptions(ctx, conn, `
select expense_status.expense_status
, esi18n.name
from expense_status
join expense_status_i18n esi18n using(expense_status)
where esi18n.lang_tag = $1
order by expense_status`, locale.Language.String())
}
func (form *expenseForm) Parse(r *http.Request) error { func (form *expenseForm) Parse(r *http.Request) error {
if err := r.ParseMultipartForm(form.File.MaxSize); err != nil { if err := r.ParseMultipartForm(form.File.MaxSize); err != nil {
return err return err
@ -252,7 +202,6 @@ func (form *expenseForm) Parse(r *http.Request) error {
if err := form.File.FillValue(r); err != nil { if err := form.File.FillValue(r); err != nil {
return err return err
} }
form.ExpenseStatus.FillValue(r)
form.Tags.FillValue(r) form.Tags.FillValue(r)
return nil return nil
} }
@ -268,20 +217,16 @@ func (form *expenseForm) Validate() bool {
} }
validator.CheckValidSelectOption(form.Tax, gettext("Selected tax is not valid.", form.locale)) validator.CheckValidSelectOption(form.Tax, gettext("Selected tax is not valid.", form.locale))
validator.CheckAtMostOneOfEachGroup(form.Tax, gettext("You can only select a tax of each class.", form.locale)) validator.CheckAtMostOneOfEachGroup(form.Tax, gettext("You can only select a tax of each class.", form.locale))
validator.CheckValidSelectOption(form.ExpenseStatus, gettext("Selected expense status is not valid.", form.locale))
return validator.AllOK() return validator.AllOK()
} }
func (form *expenseForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) bool { func (form *expenseForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) bool {
selectedExpenseStatus := form.ExpenseStatus.Selected return !notFoundErrorOrPanic(conn.QueryRow(ctx, `
form.ExpenseStatus.Clear()
if notFoundErrorOrPanic(conn.QueryRow(ctx, `
select contact_id select contact_id
, invoice_number , invoice_number
, invoice_date , invoice_date
, to_price(amount, decimal_digits) , to_price(amount, decimal_digits)
, array_agg(tax_id) , array_agg(tax_id)
, expense_status
, tags , tags
from expense from expense
left join expense_tax using (expense_id) left join expense_tax using (expense_id)
@ -292,7 +237,6 @@ func (form *expenseForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s
, invoice_date , invoice_date
, amount , amount
, decimal_digits , decimal_digits
, expense_status
, tags , tags
`, slug).Scan( `, slug).Scan(
form.Invoicer, form.Invoicer,
@ -300,12 +244,7 @@ func (form *expenseForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s
form.InvoiceDate, form.InvoiceDate,
form.Amount, form.Amount,
form.Tax, form.Tax,
form.ExpenseStatus, form.Tags))
form.Tags)) {
form.ExpenseStatus.Selected = selectedExpenseStatus
return false
}
return true
} }
func HandleAddExpense(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func HandleAddExpense(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
conn := getConn(r) conn := getConn(r)
@ -328,7 +267,7 @@ func HandleAddExpense(w http.ResponseWriter, r *http.Request, _ httprouter.Param
return return
} }
taxes := mustSliceAtoi(form.Tax.Selected) taxes := mustSliceAtoi(form.Tax.Selected)
slug := conn.MustGetText(r.Context(), "", "select add_expense($1, $2, $3, $4, $5, $6, $7, $8)", company.Id, form.ExpenseStatus, form.InvoiceDate, form.Invoicer, form.InvoiceNumber, form.Amount, taxes, form.Tags) slug := conn.MustGetText(r.Context(), "", "select add_expense($1, $2, $3, $4, $5, $6, $7)", company.Id, form.InvoiceDate, form.Invoicer, form.InvoiceNumber, form.Amount, taxes, form.Tags)
if len(form.File.Content) > 0 { if len(form.File.Content) > 0 {
conn.MustQuery(r.Context(), "select attach_to_expense($1, $2, $3, $4)", slug, form.File.OriginalFileName, form.File.ContentType, form.File.Content) conn.MustQuery(r.Context(), "select attach_to_expense($1, $2, $3, $4)", slug, form.File.OriginalFileName, form.File.ContentType, form.File.Content)
} }
@ -348,13 +287,6 @@ func HandleUpdateExpense(w http.ResponseWriter, r *http.Request, params httprout
http.Error(w, err.Error(), http.StatusForbidden) http.Error(w, err.Error(), http.StatusForbidden)
return return
} }
if r.FormValue("quick") == "status" {
slug := conn.MustGetText(r.Context(), "", "update expense set expense_status = $1 where slug = $2 returning slug", form.ExpenseStatus, params[0].Value)
if slug == "" {
http.NotFound(w, r)
}
htmxRedirect(w, r, companyURI(mustGetCompany(r), "/expenses"))
} else {
slug := params[0].Value slug := params[0].Value
if !form.Validate() { if !form.Validate() {
if !IsHTMxRequest(r) { if !IsHTMxRequest(r) {
@ -364,7 +296,7 @@ func HandleUpdateExpense(w http.ResponseWriter, r *http.Request, params httprout
return return
} }
taxes := mustSliceAtoi(form.Tax.Selected) taxes := mustSliceAtoi(form.Tax.Selected)
if found := conn.MustGetText(r.Context(), "", "select edit_expense($1, $2, $3, $4, $5, $6, $7, $8)", slug, form.ExpenseStatus, form.InvoiceDate, form.Invoicer, form.InvoiceNumber, form.Amount, taxes, form.Tags); found == "" { if found := conn.MustGetText(r.Context(), "", "select edit_expense($1, $2, $3, $4, $5, $6, $7)", slug, form.InvoiceDate, form.Invoicer, form.InvoiceNumber, form.Amount, taxes, form.Tags); found == "" {
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
@ -373,7 +305,6 @@ func HandleUpdateExpense(w http.ResponseWriter, r *http.Request, params httprout
} }
htmxRedirect(w, r, companyURI(company, "/expenses")) htmxRedirect(w, r, companyURI(company, "/expenses"))
} }
}
type expenseFilterForm struct { type expenseFilterForm struct {
locale *Locale locale *Locale
@ -382,7 +313,6 @@ type expenseFilterForm struct {
InvoiceNumber *InputField InvoiceNumber *InputField
FromDate *InputField FromDate *InputField
ToDate *InputField ToDate *InputField
ExpenseStatus *SelectField
Tags *TagsField Tags *TagsField
TagsCondition *ToggleField TagsCondition *ToggleField
} }
@ -416,12 +346,6 @@ func newExpenseFilterForm(ctx context.Context, conn *Conn, locale *Locale, compa
Name: "tags", Name: "tags",
Label: pgettext("input", "Tags", locale), Label: pgettext("input", "Tags", locale),
}, },
ExpenseStatus: &SelectField{
Name: "expense_status",
Label: pgettext("input", "Expense Status", locale),
EmptyLabel: gettext("All status", locale),
Options: mustGetExpenseStatusOptions(ctx, conn, locale),
},
TagsCondition: &ToggleField{ TagsCondition: &ToggleField{
Name: "tags_condition", Name: "tags_condition",
Label: pgettext("input", "Tags Condition", locale), Label: pgettext("input", "Tags Condition", locale),
@ -448,7 +372,6 @@ func (form *expenseFilterForm) Parse(r *http.Request) error {
form.InvoiceNumber.FillValue(r) form.InvoiceNumber.FillValue(r)
form.FromDate.FillValue(r) form.FromDate.FillValue(r)
form.ToDate.FillValue(r) form.ToDate.FillValue(r)
form.ExpenseStatus.FillValue(r)
form.Tags.FillValue(r) form.Tags.FillValue(r)
form.TagsCondition.FillValue(r) form.TagsCondition.FillValue(r)
return nil return nil
@ -475,7 +398,6 @@ func (form *expenseFilterForm) BuildQuery(args []interface{}) (string, []interfa
customerId, _ := strconv.Atoi(form.Contact.Selected[0]) customerId, _ := strconv.Atoi(form.Contact.Selected[0])
return customerId return customerId
}) })
maybeAppendWhere("expense.expense_status = $%d", form.ExpenseStatus.String(), nil)
maybeAppendWhere("invoice_number = $%d", form.InvoiceNumber.String(), nil) maybeAppendWhere("invoice_number = $%d", form.InvoiceNumber.String(), nil)
maybeAppendWhere("invoice_date >= $%d", form.FromDate.String(), nil) maybeAppendWhere("invoice_date >= $%d", form.FromDate.String(), nil)
maybeAppendWhere("invoice_date <= $%d", form.ToDate.String(), nil) maybeAppendWhere("invoice_date <= $%d", form.ToDate.String(), nil)

View File

@ -152,7 +152,7 @@ func newInvoiceFilterForm(ctx context.Context, conn *Conn, locale *Locale, compa
Name: "invoice_status", Name: "invoice_status",
Label: pgettext("input", "Invoice Status", locale), Label: pgettext("input", "Invoice Status", locale),
EmptyLabel: gettext("All status", locale), EmptyLabel: gettext("All status", locale),
Options: mustGetInvoiceStatusOptions(ctx, conn, locale), Options: MustGetOptions(ctx, conn, "select invoice_status.invoice_status, isi18n.name from invoice_status join invoice_status_i18n isi18n using(invoice_status) where isi18n.lang_tag = $1 order by invoice_status", locale.Language.String()),
}, },
InvoiceNumber: &InputField{ InvoiceNumber: &InputField{
Name: "number", Name: "number",
@ -691,7 +691,7 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
Required: true, Required: true,
Label: pgettext("input", "Invoice Status", locale), Label: pgettext("input", "Invoice Status", locale),
Selected: []string{"created"}, Selected: []string{"created"},
Options: mustGetInvoiceStatusOptions(ctx, conn, locale), Options: MustGetOptions(ctx, conn, "select invoice_status.invoice_status, isi18n.name from invoice_status join invoice_status_i18n isi18n using(invoice_status) where isi18n.lang_tag = $1 order by invoice_status", locale.Language.String()),
}, },
Customer: &SelectField{ Customer: &SelectField{
Name: "customer", Name: "customer",
@ -724,16 +724,6 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
} }
} }
func mustGetInvoiceStatusOptions(ctx context.Context, conn *Conn, locale *Locale) []*SelectOption {
return MustGetOptions(ctx, conn, `
select invoice_status.invoice_status
, isi18n.name
from invoice_status
join invoice_status_i18n isi18n using(invoice_status)
where isi18n.lang_tag = $1
order by invoice_status`, locale.Language.String())
}
func (form *invoiceForm) Parse(r *http.Request) error { func (form *invoiceForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
return err return err

View File

@ -1,52 +1,7 @@
-- Deploy numerus:add_expense to pg -- Revert numerus:add_expense from pg
-- requires: schema_numerus
-- requires: expense
-- requires: expense_tax
-- requires: tax
-- requires: company
-- requires: currency
-- requires: parse_price
-- requires: tax
-- requires: tag_name
begin; begin;
set search_path to numerus, public; drop function if exists numerus.add_expense(integer, date, integer, text, text, integer[], numerus.tag_name[]);
drop function if exists add_expense(integer, text, date, integer, text, text, integer[], tag_name[]);
create or replace function add_expense(company integer, invoice_date date, contact_id integer, invoice_number text, amount text, taxes integer[], tags tag_name[]) returns uuid as
$$
declare
eid integer;
eslug uuid;
begin
insert into expense (company_id, contact_id, invoice_number, invoice_date, amount, currency_code, tags)
select company_id
, contact_id
, invoice_number
, invoice_date
, parse_price(amount, currency.decimal_digits)
, currency_code
, tags
from company
join currency using (currency_code)
where company.company_id = add_expense.company
returning expense_id, slug
into eid, eslug;
insert into expense_tax (expense_id, tax_id, tax_rate)
select eid, tax_id, tax.rate
from tax
join unnest(taxes) as etax(tax_id) using (tax_id);
return eslug;
end;
$$
language plpgsql;
revoke execute on function add_expense(integer, date, integer, text, text, integer[], tag_name[]) from public;
grant execute on function add_expense(integer, date, integer, text, text, integer[], tag_name[]) to invoicer;
grant execute on function add_expense(integer, date, integer, text, text, integer[], tag_name[]) to admin;
commit; commit;

View File

@ -1,7 +0,0 @@
-- Revert numerus:add_expense from pg
begin;
drop function if exists numerus.add_expense(integer, date, integer, text, text, integer[], numerus.tag_name[]);
commit;

View File

@ -1,10 +0,0 @@
-- Revert numerus:available_expense_status from pg
begin;
set search_path to numerus;
delete from expense_status_i18n;
delete from expense_status;
commit;

View File

@ -1,52 +1,7 @@
-- Deploy numerus:edit_expense to pg -- Revert numerus:edit_expense from pg
-- requires: schema_numerus
-- requires: expense
-- requires: currency
-- requires: parse_price
-- requires: tax
-- requires: tag_name
begin; begin;
set search_path to numerus, public; drop function if exists numerus.edit_expense(uuid, date, integer, text, text, integer[], numerus.tag_name[]);
drop function if exists edit_expense(uuid, text, date, integer, text, text, integer[], tag_name[]);
create or replace function edit_expense(expense_slug uuid, invoice_date date, contact_id integer, invoice_number text, amount text, taxes integer[], tags tag_name[]) returns uuid as
$$
declare
eid integer;
begin
update expense
set invoice_date = edit_expense.invoice_date
, contact_id = edit_expense.contact_id
, invoice_number = edit_expense.invoice_number
, amount = parse_price(edit_expense.amount, decimal_digits)
, tags = edit_expense.tags
from currency
where slug = expense_slug
and currency.currency_code = expense.currency_code
returning expense_id
into eid;
if eid is null then
return null;
end if;
delete from expense_tax where expense_id = eid;
insert into expense_tax (expense_id, tax_id, tax_rate)
select eid, tax_id, tax.rate
from tax
join unnest(taxes) as etax(tax_id) using (tax_id);
return expense_slug;
end;
$$
language plpgsql;
revoke execute on function edit_expense(uuid, date, integer, text, text, integer[], tag_name[]) from public;
grant execute on function edit_expense(uuid, date, integer, text, text, integer[], tag_name[]) to invoicer;
grant execute on function edit_expense(uuid, date, integer, text, text, integer[], tag_name[]) to admin;
commit; commit;

View File

@ -1,7 +0,0 @@
-- Revert numerus:edit_expense from pg
begin;
drop function if exists numerus.edit_expense(uuid, date, integer, text, text, integer[], numerus.tag_name[]);
commit;

View File

@ -1,9 +0,0 @@
-- Revert numerus:expense_expense_status from pg
begin;
alter table numerus.expense
drop column if exists expense_status
;
commit;

View File

@ -1,7 +0,0 @@
-- Revert numerus:expense_status from pg
begin;
drop table if exists numerus.expense_status;
commit;

View File

@ -1,7 +0,0 @@
-- Revert numerus:expense_status_i18n from pg
begin;
drop table if exists numerus.expense_status_i18n;
commit;

View File

@ -115,10 +115,3 @@ input_is_valid [schema_public roles] 2023-07-03T08:42:46Z jordi fita mas <jordi@
input_is_valid_phone [schema_public roles extension_pg_libphonenumber] 2023-07-03T08:59:36Z jordi fita mas <jordi@tandem.blog> # add function to validate phone number inputs input_is_valid_phone [schema_public roles extension_pg_libphonenumber] 2023-07-03T08:59:36Z jordi fita mas <jordi@tandem.blog> # add function to validate phone number inputs
import_contact [schema_numerus roles contact contact_web contact_phone contact_email contact_iban contact_swift contact_tax_details input_is_valid input_is_valid_phone] 2023-07-02T18:50:22Z jordi fita mas <jordi@tandem.blog> # Add functions to massively import customer data import_contact [schema_numerus roles contact contact_web contact_phone contact_email contact_iban contact_swift contact_tax_details input_is_valid input_is_valid_phone] 2023-07-02T18:50:22Z jordi fita mas <jordi@tandem.blog> # Add functions to massively import customer data
@v1 2023-07-03T09:32:51Z jordi fita mas <jordi@tandem.blog> # Tag version 1 @v1 2023-07-03T09:32:51Z jordi fita mas <jordi@tandem.blog> # Tag version 1
expense_status [schema_numerus roles] 2023-07-11T11:56:39Z jordi fita mas <jordi@tandem.blog> # Add the relation of expense status
expense_status_i18n [schema_numerus roles expense_status language] 2023-07-11T12:09:48Z jordi fita mas <jordi@tandem.blog> # Add relation for expense status translatable texts
available_expense_status [schema_numerus expense_status expense_status_i18n] 2023-07-11T12:13:45Z jordi fita mas <jordi@tandem.blog> # Add the list of available expense status
expense_expense_status [expense] 2023-07-11T12:28:58Z jordi fita mas <jordi@tandem.blog> # Add expense_status to expense relation
add_expense [add_expense@v1 expense_status expense_expense_status] 2023-07-11T13:16:16Z jordi fita mas <jordi@tandem.blog> # Add expense_status parameter to add_expense
edit_expense [edit_expense@v1 expense_status expense_expense_status] 2023-07-11T13:21:17Z jordi fita mas <jordi@tandem.blog> # Add expense_status parameter to edit_expense

View File

@ -9,15 +9,15 @@ select plan(14);
set search_path to auth, numerus, public; set search_path to auth, numerus, public;
select has_function('numerus', 'add_expense', array ['integer', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']); select has_function('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
select function_lang_is('numerus', 'add_expense', array ['integer', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'plpgsql'); select function_lang_is('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'plpgsql');
select function_returns('numerus', 'add_expense', array ['integer', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'uuid'); select function_returns('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'uuid');
select isnt_definer('numerus', 'add_expense', array ['integer', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']); select isnt_definer('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
select volatility_is('numerus', 'add_expense', array ['integer', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'volatile'); select volatility_is('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'volatile');
select function_privs_are('numerus', 'add_expense', array ['integer', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'guest', array []::text[]); select function_privs_are('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'guest', array []::text[]);
select function_privs_are('numerus', 'add_expense', array ['integer', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'invoicer', array ['EXECUTE']); select function_privs_are('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'invoicer', array ['EXECUTE']);
select function_privs_are('numerus', 'add_expense', array ['integer', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'admin', array ['EXECUTE']); select function_privs_are('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'admin', array ['EXECUTE']);
select function_privs_are('numerus', 'add_expense', array ['integer', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'authenticator', array []::text[]); select function_privs_are('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'authenticator', array []::text[]);
set client_min_messages to warning; set client_min_messages to warning;
@ -64,25 +64,25 @@ values (12, 1, 'Contact 2.1')
select lives_ok( select lives_ok(
$$ select add_expense(1, 'pending', '2023-05-02', 12, 'Invoice 1', '11.11', '{4}', '{tag1,tag2}') $$, $$ select add_expense(1, '2023-05-02', 12, 'Invoice 1', '11.11', '{4}', '{tag1,tag2}') $$,
'Should be able to insert an expense for the first company with a tax' 'Should be able to insert an expense for the first company with a tax'
); );
select lives_ok( select lives_ok(
$$ select add_expense(1, 'paid', '2023-05-03', 13, 'Invoice 2', '22.22', '{4,3}', '{}') $$, $$ select add_expense(1, '2023-05-03', 13, 'Invoice 2', '22.22', '{4,3}', '{}') $$,
'Should be able to insert a second expense for the first company with two taxes' 'Should be able to insert a second expense for the first company with two taxes'
); );
select lives_ok( select lives_ok(
$$ select add_expense(2, 'pending', '2023-05-04', 15, 'Invoice 3', '33.33', '{}', '{tag3}') $$, $$ select add_expense(2, '2023-05-04', 15, 'Invoice 3', '33.33', '{}', '{tag3}') $$,
'Should be able to insert an invoice for the second company with no tax' 'Should be able to insert an invoice for the second company with no tax'
); );
select bag_eq( select bag_eq(
$$ select company_id, invoice_number, invoice_date, contact_id, amount, currency_code, expense_status, tags, created_at from expense $$, $$ select company_id, invoice_number, invoice_date, contact_id, amount, currency_code, tags, created_at from expense $$,
$$ values (1, 'Invoice 1', '2023-05-02'::date, 12, 1111, 'EUR', 'pending', '{tag1,tag2}'::tag_name[], current_timestamp) $$ values (1, 'Invoice 1', '2023-05-02'::date, 12, 1111, 'EUR', '{tag1,tag2}'::tag_name[], current_timestamp)
, (1, 'Invoice 2', '2023-05-03'::date, 13, 2222, 'EUR', 'paid', '{}'::tag_name[], current_timestamp) , (1, 'Invoice 2', '2023-05-03'::date, 13, 2222, 'EUR', '{}'::tag_name[], current_timestamp)
, (2, 'Invoice 3', '2023-05-04'::date, 15, 3333, 'USD', 'pending', '{tag3}'::tag_name[], current_timestamp) , (2, 'Invoice 3', '2023-05-04'::date, 15, 3333, 'USD', '{tag3}'::tag_name[], current_timestamp)
$$, $$,
'Should have created all expenses' 'Should have created all expenses'
); );

View File

@ -9,15 +9,15 @@ select plan(13);
set search_path to auth, numerus, public; set search_path to auth, numerus, public;
select has_function('numerus', 'edit_expense', array ['uuid', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']); select has_function('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
select function_lang_is('numerus', 'edit_expense', array ['uuid', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'plpgsql'); select function_lang_is('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'plpgsql');
select function_returns('numerus', 'edit_expense', array ['uuid', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'uuid'); select function_returns('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'uuid');
select isnt_definer('numerus', 'edit_expense', array ['uuid', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']); select isnt_definer('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
select volatility_is('numerus', 'edit_expense', array ['uuid', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'volatile'); select volatility_is('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'volatile');
select function_privs_are('numerus', 'edit_expense', array ['uuid', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'guest', array []::text[]); select function_privs_are('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'guest', array []::text[]);
select function_privs_are('numerus', 'edit_expense', array ['uuid', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'invoicer', array ['EXECUTE']); select function_privs_are('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'invoicer', array ['EXECUTE']);
select function_privs_are('numerus', 'edit_expense', array ['uuid', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'admin', array ['EXECUTE']); select function_privs_are('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'admin', array ['EXECUTE']);
select function_privs_are('numerus', 'edit_expense', array ['uuid', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'authenticator', array []::text[]); select function_privs_are('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'authenticator', array []::text[]);
set client_min_messages to warning; set client_min_messages to warning;
@ -58,9 +58,9 @@ values (12, 1, 'Contact 2.1')
, (13, 1, 'Contact 2.2') , (13, 1, 'Contact 2.2')
; ;
insert into expense (expense_id, company_id, slug, invoice_number, invoice_date, contact_id, amount, currency_code, expense_status, tags) insert into expense (expense_id, company_id, slug, invoice_number, invoice_date, contact_id, amount, currency_code, tags)
values (15, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'INV1', '2023-05-04', 12, 111, 'EUR', 'pending', '{tag1}') values (15, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'INV1', '2023-05-04', 12, 111, 'EUR', '{tag1}')
, (16, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'INV2', '2023-05-05', 13, 222, 'EUR', 'paid', '{tag2}') , (16, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'INV2', '2023-05-05', 13, 222, 'EUR', '{tag2}')
; ;
insert into expense_tax (expense_id, tax_id, tax_rate) insert into expense_tax (expense_id, tax_id, tax_rate)
@ -70,19 +70,19 @@ values (15, 4, 0.21)
; ;
select lives_ok( select lives_ok(
$$ select edit_expense('7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'paid', '2023-05-06', 13, 'INV11', '1.12', '{4}', array['tag1']) $$, $$ select edit_expense('7ac3ae0e-b0c1-4206-a19b-0be20835edd4', '2023-05-06', 13, 'INV11', '1.12', '{4}', array['tag1']) $$,
'Should be able to edit the first expense' 'Should be able to edit the first expense'
); );
select lives_ok( select lives_ok(
$$ select edit_expense('b57b980b-247b-4be4-a0b7-03a7819c53ae', 'pending', '2023-05-07', 12, 'INV22', '3.33', '{4,3}', array['tag1', 'tag3']) $$, $$ select edit_expense('b57b980b-247b-4be4-a0b7-03a7819c53ae', '2023-05-07', 12, 'INV22', '3.33', '{4,3}', array['tag1', 'tag3']) $$,
'Should be able to edit the second expense' 'Should be able to edit the second expense'
); );
select bag_eq( select bag_eq(
$$ select invoice_number, invoice_date, contact_id, amount, expense_status, tags from expense $$, $$ select invoice_number, invoice_date, contact_id, amount, tags from expense $$,
$$ values ('INV11', '2023-05-06'::date, 13, 112, 'paid', '{tag1}'::tag_name[]) $$ values ('INV11', '2023-05-06'::date, 13, 112, '{tag1}'::tag_name[])
, ('INV22', '2023-05-07'::date, 12, 333, 'pending', '{tag1,tag3}'::tag_name[]) , ('INV22', '2023-05-07'::date, 12, 333, '{tag1,tag3}'::tag_name[])
$$, $$,
'Should have updated all expenses' 'Should have updated all expenses'
); );

View File

@ -5,7 +5,7 @@ reset client_min_messages;
begin; begin;
select plan(74); select plan(67);
set search_path to numerus, auth, public; set search_path to numerus, auth, public;
@ -65,14 +65,6 @@ select col_type_is('expense', 'amount', 'integer');
select col_not_null('expense', 'amount'); select col_not_null('expense', 'amount');
select col_hasnt_default('expense', 'amount'); select col_hasnt_default('expense', 'amount');
select has_column('expense', 'expense_status');
select col_is_fk('expense', 'expense_status');
select fk_ok('expense', 'expense_status', 'expense_status', 'expense_status');
select col_type_is('expense', 'expense_status', 'text');
select col_not_null('expense', 'expense_status');
select col_has_default('expense', 'expense_status');
select col_default_is('expense', 'expense_status', 'pending');
select has_column('expense', 'currency_code'); select has_column('expense', 'currency_code');
select col_is_fk('expense', 'currency_code'); select col_is_fk('expense', 'currency_code');
select fk_ok('expense', 'currency_code', 'currency', 'currency_code'); select fk_ok('expense', 'currency_code', 'currency', 'currency_code');

View File

@ -1,33 +0,0 @@
-- Test expense_status
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(15);
set search_path to numerus, public;
select has_table('expense_status');
select has_pk('expense_status' );
select table_privs_are('expense_status', 'guest', array []::text[]);
select table_privs_are('expense_status', 'invoicer', array ['SELECT']);
select table_privs_are('expense_status', 'admin', array ['SELECT']);
select table_privs_are('expense_status', 'authenticator', array []::text[]);
select has_column('expense_status', 'expense_status');
select col_is_pk('expense_status', 'expense_status');
select col_type_is('expense_status', 'expense_status', 'text');
select col_not_null('expense_status', 'expense_status');
select col_hasnt_default('expense_status', 'expense_status');
select has_column('expense_status', 'name');
select col_type_is('expense_status', 'name', 'text');
select col_not_null('expense_status', 'name');
select col_hasnt_default('expense_status', 'name');
select *
from finish();
rollback;

View File

@ -1,44 +0,0 @@
-- Test expense_status_i18n
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(23);
set search_path to numerus, public;
select has_table('expense_status_i18n');
select has_pk('expense_status_i18n' );
select col_is_pk('expense_status_i18n', array['expense_status', 'lang_tag']);
select table_privs_are('expense_status_i18n', 'guest', array []::text[]);
select table_privs_are('expense_status_i18n', 'invoicer', array ['SELECT']);
select table_privs_are('expense_status_i18n', 'admin', array ['SELECT']);
select table_privs_are('expense_status_i18n', 'authenticator', array []::text[]);
select has_column('expense_status_i18n', 'expense_status');
select col_is_fk('expense_status_i18n', 'expense_status');
select fk_ok('expense_status_i18n', 'expense_status', 'expense_status', 'expense_status');
select col_type_is('expense_status_i18n', 'expense_status', 'text');
select col_not_null('expense_status_i18n', 'expense_status');
select col_hasnt_default('expense_status_i18n', 'expense_status');
select has_column('expense_status_i18n', 'lang_tag');
select col_is_fk('expense_status_i18n', 'lang_tag');
select fk_ok('expense_status_i18n', 'lang_tag', 'language', 'lang_tag');
select col_type_is('expense_status_i18n', 'lang_tag', 'text');
select col_not_null('expense_status_i18n', 'lang_tag');
select col_hasnt_default('expense_status_i18n', 'lang_tag');
select has_column('expense_status_i18n', 'name');
select col_type_is('expense_status_i18n', 'name', 'text');
select col_not_null('expense_status_i18n', 'name');
select col_hasnt_default('expense_status_i18n', 'name');
select *
from finish();
rollback;

View File

@ -2,6 +2,6 @@
begin; begin;
select has_function_privilege('numerus.add_expense(integer, text, date, integer, text, text, integer[], numerus.tag_name[])', 'execute'); select has_function_privilege('numerus.add_expense(integer, date, integer, text, text, integer[], numerus.tag_name[])', 'execute');
rollback; rollback;

View File

@ -1,7 +0,0 @@
-- Verify numerus:add_expense on pg
begin;
select has_function_privilege('numerus.add_expense(integer, date, integer, text, text, integer[], numerus.tag_name[])', 'execute');
rollback;

View File

@ -1,15 +0,0 @@
-- Verify numerus:available_expense_status on pg
begin;
set search_path to numerus;
select 1 / count(*) from expense_status where expense_status = 'pending' and name ='Pending';
select 1 / count(*) from expense_status where expense_status = 'paid' and name ='Paid';
select 1 / count(*) from expense_status_i18n where expense_status = 'pending' and name ='Pendent' and lang_tag = 'ca';
select 1 / count(*) from expense_status_i18n where expense_status = 'pending' and name ='Pendiente' and lang_tag = 'es';
select 1 / count(*) from expense_status_i18n where expense_status = 'paid' and name ='Pagada' and lang_tag= 'ca';
select 1 / count(*) from expense_status_i18n where expense_status = 'paid' and name ='Pagada' and lang_tag= 'es';
rollback;

View File

@ -2,6 +2,6 @@
begin; begin;
select has_function_privilege('numerus.edit_expense(uuid, text, date, integer, text, text, integer[], numerus.tag_name[])', 'execute'); select has_function_privilege('numerus.edit_expense(uuid, date, integer, text, text, integer[], numerus.tag_name[])', 'execute');
rollback; rollback;

View File

@ -1,7 +0,0 @@
-- Verify numerus:edit_expense on pg
begin;
select has_function_privilege('numerus.edit_expense(uuid, date, integer, text, text, integer[], numerus.tag_name[])', 'execute');
rollback;

View File

@ -1,10 +0,0 @@
-- Verify numerus:expense_expense_status on pg
begin;
select expense_status
from numerus.expense
where false
;
rollback;

View File

@ -1,10 +0,0 @@
-- Verify numerus:expense_status on pg
begin;
select expense_status
, name
from numerus.expense_status
where false;
rollback;

View File

@ -1,11 +0,0 @@
-- Verify numerus:expense_status_i18n on pg
begin;
select expense_status
, lang_tag
, name
from numerus.expense_status_i18n
where false;
rollback;

View File

@ -194,10 +194,6 @@ p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word; overflow-wrap: break-word;
} }
:any-link {
color: #0000ff;
}
input[type="radio"] { input[type="radio"] {
accent-color: var(--numerus--color--black); accent-color: var(--numerus--color--black);
} }
@ -614,19 +610,16 @@ main > nav {
} }
.quote-status, .quote-status,
.expense-status,
.invoice-status { .invoice-status {
position: relative; position: relative;
} }
.quote-status summary, .quote-status summary,
.expense-status summary,
.invoice-status summary { .invoice-status summary {
height: 3rem; height: 3rem;
} }
.quote-status ul, .quote-status ul,
.expense-status ul,
.invoice-status ul { .invoice-status ul {
position: absolute; position: absolute;
top: 0; top: 0;
@ -638,14 +631,12 @@ main > nav {
} }
.quote-status button, .quote-status button,
.expense-status button,
.invoice-status button { .invoice-status button {
border: 0; border: 0;
min-width: 15rem; min-width: 15rem;
} }
[class^='quote-status-'], [class^='quote-status-'],
[class^='expense-status-'],
[class^='invoice-status-'] { [class^='invoice-status-'] {
text-align: center; text-align: center;
text-transform: uppercase; text-transform: uppercase;
@ -663,13 +654,11 @@ main > nav {
} }
.quote-status-accepted, .quote-status-accepted,
.expense-status-paid,
.invoice-status-paid { .invoice-status-paid {
background-color: var(--numerus--color--light-green); background-color: var(--numerus--color--light-green);
} }
.quote-status-rejected, .quote-status-rejected,
.expense-status-pending,
.invoice-status-unpaid { .invoice-status-unpaid {
background-color: var(--numerus--color--rosy); background-color: var(--numerus--color--rosy);
} }
@ -710,6 +699,11 @@ main > nav {
/* expenses */ /* expenses */
.expenses-data div:last-child {
grid-column-start: 3;
grid-column-end: 5;
}
/* product */ /* product */
.product-data div:last-child { .product-data div:last-child {
grid-column-start: 1; grid-column-start: 1;

View File

@ -18,8 +18,7 @@
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.editExpensePage*/ -}} {{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.editExpensePage*/ -}}
<section id="new-expense-dialog-content" data-hx-target="main"> <section id="new-expense-dialog-content" data-hx-target="main">
<h2>{{ printf (pgettext "Edit Expense “%s”" "title") .Slug }}</h2> <h2>{{ printf (pgettext "Edit Expense “%s”" "title") .Slug }}</h2>
<form enctype="multipart/form-data" method="POST" action="{{ companyURI "/expenses/" }}{{ .Slug }}" <form enctype="multipart/form-data" method="POST" action="{{ companyURI "/expenses/" }}{{ .Slug }}" data-hx-boost="true">
data-hx-boost="true">
{{ csrfToken }} {{ csrfToken }}
{{ putMethod }} {{ putMethod }}
@ -31,7 +30,6 @@
{{ template "input-field" .Amount }} {{ template "input-field" .Amount }}
{{ template "select-field" .Tax }} {{ template "select-field" .Tax }}
{{ template "tags-field" .Tags }} {{ template "tags-field" .Tags }}
{{ template "select-field" .ExpenseStatus }}
{{ template "file-field" .File }} {{ template "file-field" .File }}
</div> </div>
{{- end }} {{- end }}

View File

@ -26,7 +26,6 @@
> >
{{ with .Filters }} {{ with .Filters }}
{{ template "select-field" .Contact }} {{ template "select-field" .Contact }}
{{ template "select-field" .ExpenseStatus }}
{{ template "input-field" .FromDate }} {{ template "input-field" .FromDate }}
{{ template "input-field" .ToDate }} {{ template "input-field" .ToDate }}
{{ template "input-field" .InvoiceNumber }} {{ template "input-field" .InvoiceNumber }}
@ -43,7 +42,6 @@
<th>{{( pgettext "Contact" "title" )}}</th> <th>{{( pgettext "Contact" "title" )}}</th>
<th>{{( pgettext "Invoice Date" "title" )}}</th> <th>{{( pgettext "Invoice Date" "title" )}}</th>
<th>{{( pgettext "Invoice Number" "title" )}}</th> <th>{{( pgettext "Invoice Number" "title" )}}</th>
<th>{{( pgettext "Status" "title" )}}</th>
<th>{{( pgettext "Tags" "title" )}}</th> <th>{{( pgettext "Tags" "title" )}}</th>
<th>{{( pgettext "Amount" "title" )}}</th> <th>{{( pgettext "Amount" "title" )}}</th>
<th>{{( pgettext "Download" "title" )}}</th> <th>{{( pgettext "Download" "title" )}}</th>
@ -52,33 +50,11 @@
</thead> </thead>
<tbody> <tbody>
{{ with .Expenses }} {{ with .Expenses }}
{{- range $expense := . }} {{- range . }}
<tr> <tr>
<td>{{ .InvoicerName }}</td> <td>{{ .InvoicerName }}</td>
<td>{{ .InvoiceDate|formatDate }}</td> <td>{{ .InvoiceDate|formatDate }}</td>
<td>{{ .InvoiceNumber }}</td> <td>{{ .InvoiceNumber }}</td>
<td>
<details class="expense-status menu">
<summary class="expense-status-{{ .Status }}">{{ .StatusLabel }}</summary>
<form action="{{companyURI "/expenses/"}}{{ .Slug }}" enctype="multipart/form-data" method="POST" data-hx-boost="true">
{{ csrfToken }}
{{ putMethod }}
<input type="hidden" name="quick" value="status">
<ul role="menu">
{{- range $status, $name := $.ExpenseStatuses }}
{{- if ne $status $expense.Status }}
<li role="presentation">
<button role="menuitem" type="submit"
name="expense_status" value="{{ $status }}"
class="expense-status-{{ $status }}"
>{{ $name }}</button>
</li>
{{- end }}
{{- end }}
</ul>
</form>
</details>
</td>
<td <td
data-hx-get="{{companyURI "/expenses/"}}{{ .Slug }}/tags/edit" data-hx-get="{{companyURI "/expenses/"}}{{ .Slug }}/tags/edit"
data-hx-target="this" data-hx-target="this"

View File

@ -27,7 +27,6 @@
{{ template "input-field" .Amount }} {{ template "input-field" .Amount }}
{{ template "select-field" .Tax }} {{ template "select-field" .Tax }}
{{ template "tags-field" .Tags }} {{ template "tags-field" .Tags }}
{{ template "select-field" .ExpenseStatus }}
{{ template "file-field" .File }} {{ template "file-field" .File }}
</div> </div>
<fieldset> <fieldset>