Add expenses statuses
We only want two statuses for expense: not yet paid (pending), and paid. Thus, it is a bit different from quotes and invoices, because expenses do not pass throw the “workflow” of created→sent→{pending,paid}. That’s way in this case the status field is already in the new expense form, instead of hidden, and by pending is not equivalent to created but unpaid (i.e., the same status color). With the new select field in the form, the file field no longer can span two columns or it would be alone on the next row. Closes #67.
This commit is contained in:
parent
b7578a56df
commit
b48a974086
|
@ -8,24 +8,27 @@
|
|||
-- requires: parse_price
|
||||
-- requires: tax
|
||||
-- requires: tag_name
|
||||
-- requires: expense_status
|
||||
-- requires: expense_expense_status
|
||||
|
||||
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
|
||||
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
|
||||
$$
|
||||
declare
|
||||
eid integer;
|
||||
eslug uuid;
|
||||
begin
|
||||
insert into expense (company_id, contact_id, invoice_number, invoice_date, amount, currency_code, tags)
|
||||
insert into expense (company_id, contact_id, invoice_number, invoice_date, amount, currency_code, expense_status, tags)
|
||||
select company_id
|
||||
, contact_id
|
||||
, invoice_number
|
||||
, invoice_date
|
||||
, parse_price(amount, currency.decimal_digits)
|
||||
, currency_code
|
||||
, status
|
||||
, tags
|
||||
from company
|
||||
join currency using (currency_code)
|
||||
|
@ -43,8 +46,10 @@ 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;
|
||||
revoke execute on function add_expense(integer, text, 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, text, date, integer, text, text, integer[], tag_name[]) to admin;
|
||||
|
||||
drop function if exists add_expense(integer, date, integer, text, text, integer[], tag_name[]);
|
||||
|
||||
commit;
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
-- 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;
|
|
@ -0,0 +1,22 @@
|
|||
-- 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;
|
|
@ -5,12 +5,14 @@
|
|||
-- requires: parse_price
|
||||
-- requires: tax
|
||||
-- requires: tag_name
|
||||
-- requires: expense_status
|
||||
-- requires: expense_expense_status
|
||||
|
||||
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
|
||||
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
|
||||
$$
|
||||
declare
|
||||
eid integer;
|
||||
|
@ -20,6 +22,7 @@ begin
|
|||
, contact_id = edit_expense.contact_id
|
||||
, invoice_number = edit_expense.invoice_number
|
||||
, amount = parse_price(edit_expense.amount, decimal_digits)
|
||||
, expense_status = status
|
||||
, tags = edit_expense.tags
|
||||
from currency
|
||||
where slug = expense_slug
|
||||
|
@ -43,8 +46,8 @@ 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;
|
||||
revoke execute on function edit_expense(uuid, text, 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, text, date, integer, text, text, integer[], tag_name[]) to admin;
|
||||
|
||||
commit;
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
-- 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;
|
|
@ -0,0 +1,12 @@
|
|||
-- 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;
|
|
@ -0,0 +1,17 @@
|
|||
-- 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;
|
|
@ -0,0 +1,21 @@
|
|||
-- 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;
|
132
pkg/expenses.go
132
pkg/expenses.go
|
@ -20,12 +20,15 @@ type ExpenseEntry struct {
|
|||
InvoicerName string
|
||||
OriginalFileName string
|
||||
Tags []string
|
||||
Status string
|
||||
StatusLabel string
|
||||
}
|
||||
|
||||
type expensesIndexPage struct {
|
||||
Expenses []*ExpenseEntry
|
||||
TotalAmount string
|
||||
Filters *expenseFilterForm
|
||||
Expenses []*ExpenseEntry
|
||||
TotalAmount string
|
||||
Filters *expenseFilterForm
|
||||
ExpenseStatuses map[string]string
|
||||
}
|
||||
|
||||
func IndexExpenses(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
|
@ -38,15 +41,16 @@ func IndexExpenses(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
|
|||
return
|
||||
}
|
||||
page := &expensesIndexPage{
|
||||
Expenses: mustCollectExpenseEntries(r.Context(), conn, filters),
|
||||
TotalAmount: mustComputeExpensesTotalAmount(r.Context(), conn, filters),
|
||||
Filters: filters,
|
||||
Expenses: mustCollectExpenseEntries(r.Context(), conn, locale, filters),
|
||||
TotalAmount: mustComputeExpensesTotalAmount(r.Context(), conn, filters),
|
||||
ExpenseStatuses: mustCollectExpenseStatuses(r.Context(), conn, locale),
|
||||
Filters: filters,
|
||||
}
|
||||
mustRenderMainTemplate(w, r, "expenses/index.gohtml", page)
|
||||
}
|
||||
|
||||
func mustCollectExpenseEntries(ctx context.Context, conn *Conn, filters *expenseFilterForm) []*ExpenseEntry {
|
||||
where, args := filters.BuildQuery(nil)
|
||||
func mustCollectExpenseEntries(ctx context.Context, conn *Conn, locale *Locale, filters *expenseFilterForm) []*ExpenseEntry {
|
||||
where, args := filters.BuildQuery([]interface{}{locale.Language.String()})
|
||||
rows := conn.MustQuery(ctx, fmt.Sprintf(`
|
||||
select expense.slug
|
||||
, invoice_date
|
||||
|
@ -55,9 +59,12 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, filters *expense
|
|||
, contact.name
|
||||
, coalesce(attachment.original_filename, '')
|
||||
, expense.tags
|
||||
, expense.expense_status
|
||||
, esi18n.name
|
||||
from expense
|
||||
left join expense_attachment as attachment using (expense_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)
|
||||
where (%s)
|
||||
order by invoice_date
|
||||
|
@ -67,7 +74,7 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, filters *expense
|
|||
var entries []*ExpenseEntry
|
||||
for rows.Next() {
|
||||
entry := &ExpenseEntry{}
|
||||
if err := rows.Scan(&entry.Slug, &entry.InvoiceDate, &entry.InvoiceNumber, &entry.Amount, &entry.InvoicerName, &entry.OriginalFileName, &entry.Tags); err != nil {
|
||||
if err := rows.Scan(&entry.Slug, &entry.InvoiceDate, &entry.InvoiceNumber, &entry.Amount, &entry.InvoicerName, &entry.OriginalFileName, &entry.Tags, &entry.Status, &entry.StatusLabel); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
|
@ -79,6 +86,31 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, filters *expense
|
|||
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 {
|
||||
where, args := filters.BuildQuery(nil)
|
||||
return conn.MustGetText(ctx, "0", fmt.Sprintf(`
|
||||
|
@ -138,6 +170,7 @@ type expenseForm struct {
|
|||
Tax *SelectField
|
||||
Amount *InputField
|
||||
File *FileField
|
||||
ExpenseStatus *SelectField
|
||||
Tags *TagsField
|
||||
}
|
||||
|
||||
|
@ -183,6 +216,13 @@ func newExpenseForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
|||
Label: pgettext("input", "File", locale),
|
||||
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{
|
||||
Name: "tags",
|
||||
Label: pgettext("input", "Tags", locale),
|
||||
|
@ -190,6 +230,16 @@ 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 {
|
||||
if err := r.ParseMultipartForm(form.File.MaxSize); err != nil {
|
||||
return err
|
||||
|
@ -202,6 +252,7 @@ func (form *expenseForm) Parse(r *http.Request) error {
|
|||
if err := form.File.FillValue(r); err != nil {
|
||||
return err
|
||||
}
|
||||
form.ExpenseStatus.FillValue(r)
|
||||
form.Tags.FillValue(r)
|
||||
return nil
|
||||
}
|
||||
|
@ -217,16 +268,20 @@ func (form *expenseForm) Validate() bool {
|
|||
}
|
||||
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.CheckValidSelectOption(form.ExpenseStatus, gettext("Selected expense status is not valid.", form.locale))
|
||||
return validator.AllOK()
|
||||
}
|
||||
|
||||
func (form *expenseForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) bool {
|
||||
return !notFoundErrorOrPanic(conn.QueryRow(ctx, `
|
||||
selectedExpenseStatus := form.ExpenseStatus.Selected
|
||||
form.ExpenseStatus.Clear()
|
||||
if notFoundErrorOrPanic(conn.QueryRow(ctx, `
|
||||
select contact_id
|
||||
, invoice_number
|
||||
, invoice_date
|
||||
, to_price(amount, decimal_digits)
|
||||
, array_agg(tax_id)
|
||||
, expense_status
|
||||
, tags
|
||||
from expense
|
||||
left join expense_tax using (expense_id)
|
||||
|
@ -237,6 +292,7 @@ func (form *expenseForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s
|
|||
, invoice_date
|
||||
, amount
|
||||
, decimal_digits
|
||||
, expense_status
|
||||
, tags
|
||||
`, slug).Scan(
|
||||
form.Invoicer,
|
||||
|
@ -244,7 +300,12 @@ func (form *expenseForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s
|
|||
form.InvoiceDate,
|
||||
form.Amount,
|
||||
form.Tax,
|
||||
form.Tags))
|
||||
form.ExpenseStatus,
|
||||
form.Tags)) {
|
||||
form.ExpenseStatus.Selected = selectedExpenseStatus
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func HandleAddExpense(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
conn := getConn(r)
|
||||
|
@ -267,7 +328,7 @@ func HandleAddExpense(w http.ResponseWriter, r *http.Request, _ httprouter.Param
|
|||
return
|
||||
}
|
||||
taxes := mustSliceAtoi(form.Tax.Selected)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
@ -287,23 +348,31 @@ func HandleUpdateExpense(w http.ResponseWriter, r *http.Request, params httprout
|
|||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
slug := params[0].Value
|
||||
if !form.Validate() {
|
||||
if !IsHTMxRequest(r) {
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
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)
|
||||
}
|
||||
mustRenderEditExpenseForm(w, r, slug, form)
|
||||
return
|
||||
htmxRedirect(w, r, companyURI(mustGetCompany(r), "/expenses"))
|
||||
} else {
|
||||
slug := params[0].Value
|
||||
if !form.Validate() {
|
||||
if !IsHTMxRequest(r) {
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
}
|
||||
mustRenderEditExpenseForm(w, r, slug, form)
|
||||
return
|
||||
}
|
||||
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 == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
htmxRedirect(w, r, companyURI(company, "/expenses"))
|
||||
}
|
||||
taxes := mustSliceAtoi(form.Tax.Selected)
|
||||
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)
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
htmxRedirect(w, r, companyURI(company, "/expenses"))
|
||||
}
|
||||
|
||||
type expenseFilterForm struct {
|
||||
|
@ -313,6 +382,7 @@ type expenseFilterForm struct {
|
|||
InvoiceNumber *InputField
|
||||
FromDate *InputField
|
||||
ToDate *InputField
|
||||
ExpenseStatus *SelectField
|
||||
Tags *TagsField
|
||||
TagsCondition *ToggleField
|
||||
}
|
||||
|
@ -346,6 +416,12 @@ func newExpenseFilterForm(ctx context.Context, conn *Conn, locale *Locale, compa
|
|||
Name: "tags",
|
||||
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{
|
||||
Name: "tags_condition",
|
||||
Label: pgettext("input", "Tags Condition", locale),
|
||||
|
@ -372,6 +448,7 @@ func (form *expenseFilterForm) Parse(r *http.Request) error {
|
|||
form.InvoiceNumber.FillValue(r)
|
||||
form.FromDate.FillValue(r)
|
||||
form.ToDate.FillValue(r)
|
||||
form.ExpenseStatus.FillValue(r)
|
||||
form.Tags.FillValue(r)
|
||||
form.TagsCondition.FillValue(r)
|
||||
return nil
|
||||
|
@ -398,6 +475,7 @@ func (form *expenseFilterForm) BuildQuery(args []interface{}) (string, []interfa
|
|||
customerId, _ := strconv.Atoi(form.Contact.Selected[0])
|
||||
return customerId
|
||||
})
|
||||
maybeAppendWhere("expense.expense_status = $%d", form.ExpenseStatus.String(), nil)
|
||||
maybeAppendWhere("invoice_number = $%d", form.InvoiceNumber.String(), nil)
|
||||
maybeAppendWhere("invoice_date >= $%d", form.FromDate.String(), nil)
|
||||
maybeAppendWhere("invoice_date <= $%d", form.ToDate.String(), nil)
|
||||
|
|
|
@ -1,7 +1,52 @@
|
|||
-- Revert numerus:add_expense from pg
|
||||
-- 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;
|
||||
|
||||
drop function if exists numerus.add_expense(integer, date, integer, text, text, integer[], numerus.tag_name[]);
|
||||
set search_path to numerus, public;
|
||||
|
||||
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;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:add_expense from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop function if exists numerus.add_expense(integer, date, integer, text, text, integer[], numerus.tag_name[]);
|
||||
|
||||
commit;
|
|
@ -0,0 +1,10 @@
|
|||
-- Revert numerus:available_expense_status from pg
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to numerus;
|
||||
|
||||
delete from expense_status_i18n;
|
||||
delete from expense_status;
|
||||
|
||||
commit;
|
|
@ -1,7 +1,52 @@
|
|||
-- Revert numerus:edit_expense from pg
|
||||
-- Deploy numerus:edit_expense to pg
|
||||
-- requires: schema_numerus
|
||||
-- requires: expense
|
||||
-- requires: currency
|
||||
-- requires: parse_price
|
||||
-- requires: tax
|
||||
-- requires: tag_name
|
||||
|
||||
begin;
|
||||
|
||||
drop function if exists numerus.edit_expense(uuid, date, integer, text, text, integer[], numerus.tag_name[]);
|
||||
set search_path to numerus, public;
|
||||
|
||||
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;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:edit_expense from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop function if exists numerus.edit_expense(uuid, date, integer, text, text, integer[], numerus.tag_name[]);
|
||||
|
||||
commit;
|
|
@ -0,0 +1,9 @@
|
|||
-- Revert numerus:expense_expense_status from pg
|
||||
|
||||
begin;
|
||||
|
||||
alter table numerus.expense
|
||||
drop column if exists expense_status
|
||||
;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:expense_status from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop table if exists numerus.expense_status;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert numerus:expense_status_i18n from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop table if exists numerus.expense_status_i18n;
|
||||
|
||||
commit;
|
|
@ -115,3 +115,10 @@ 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
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
@ -9,15 +9,15 @@ select plan(14);
|
|||
|
||||
set search_path to auth, numerus, public;
|
||||
|
||||
select has_function('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
|
||||
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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'uuid');
|
||||
select isnt_definer('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
|
||||
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', '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[]'], 'invoicer', 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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'authenticator', array []::text[]);
|
||||
select has_function('numerus', 'add_expense', array ['integer', 'text', '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_returns('numerus', 'add_expense', array ['integer', 'text', '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 volatility_is('numerus', 'add_expense', array ['integer', 'text', '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', 'text', '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', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'authenticator', array []::text[]);
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
|
@ -64,25 +64,25 @@ values (12, 1, 'Contact 2.1')
|
|||
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_expense(1, '2023-05-02', 12, 'Invoice 1', '11.11', '{4}', '{tag1,tag2}') $$,
|
||||
$$ select add_expense(1, 'pending', '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'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_expense(1, '2023-05-03', 13, 'Invoice 2', '22.22', '{4,3}', '{}') $$,
|
||||
$$ select add_expense(1, 'paid', '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'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select add_expense(2, '2023-05-04', 15, 'Invoice 3', '33.33', '{}', '{tag3}') $$,
|
||||
$$ select add_expense(2, 'pending', '2023-05-04', 15, 'Invoice 3', '33.33', '{}', '{tag3}') $$,
|
||||
'Should be able to insert an invoice for the second company with no tax'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ 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', '{tag1,tag2}'::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', '{tag3}'::tag_name[], current_timestamp)
|
||||
$$ select company_id, invoice_number, invoice_date, contact_id, amount, currency_code, expense_status, tags, created_at from expense $$,
|
||||
$$ values (1, 'Invoice 1', '2023-05-02'::date, 12, 1111, 'EUR', 'pending', '{tag1,tag2}'::tag_name[], current_timestamp)
|
||||
, (1, 'Invoice 2', '2023-05-03'::date, 13, 2222, 'EUR', 'paid', '{}'::tag_name[], current_timestamp)
|
||||
, (2, 'Invoice 3', '2023-05-04'::date, 15, 3333, 'USD', 'pending', '{tag3}'::tag_name[], current_timestamp)
|
||||
$$,
|
||||
'Should have created all expenses'
|
||||
);
|
||||
|
|
|
@ -9,15 +9,15 @@ select plan(13);
|
|||
|
||||
set search_path to auth, numerus, public;
|
||||
|
||||
select has_function('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
|
||||
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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'uuid');
|
||||
select isnt_definer('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
|
||||
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', '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[]'], 'invoicer', 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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'authenticator', array []::text[]);
|
||||
select has_function('numerus', 'edit_expense', array ['uuid', 'text', '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_returns('numerus', 'edit_expense', array ['uuid', 'text', '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 volatility_is('numerus', 'edit_expense', array ['uuid', 'text', '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', 'text', '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', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'authenticator', array []::text[]);
|
||||
|
||||
|
||||
set client_min_messages to warning;
|
||||
|
@ -58,9 +58,9 @@ values (12, 1, 'Contact 2.1')
|
|||
, (13, 1, 'Contact 2.2')
|
||||
;
|
||||
|
||||
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', '{tag1}')
|
||||
, (16, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'INV2', '2023-05-05', 13, 222, 'EUR', '{tag2}')
|
||||
insert into expense (expense_id, company_id, slug, invoice_number, invoice_date, contact_id, amount, currency_code, expense_status, tags)
|
||||
values (15, 1, '7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'INV1', '2023-05-04', 12, 111, 'EUR', 'pending', '{tag1}')
|
||||
, (16, 1, 'b57b980b-247b-4be4-a0b7-03a7819c53ae', 'INV2', '2023-05-05', 13, 222, 'EUR', 'paid', '{tag2}')
|
||||
;
|
||||
|
||||
insert into expense_tax (expense_id, tax_id, tax_rate)
|
||||
|
@ -70,19 +70,19 @@ values (15, 4, 0.21)
|
|||
;
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_expense('7ac3ae0e-b0c1-4206-a19b-0be20835edd4', '2023-05-06', 13, 'INV11', '1.12', '{4}', array['tag1']) $$,
|
||||
$$ select edit_expense('7ac3ae0e-b0c1-4206-a19b-0be20835edd4', 'paid', '2023-05-06', 13, 'INV11', '1.12', '{4}', array['tag1']) $$,
|
||||
'Should be able to edit the first expense'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select edit_expense('b57b980b-247b-4be4-a0b7-03a7819c53ae', '2023-05-07', 12, 'INV22', '3.33', '{4,3}', array['tag1', 'tag3']) $$,
|
||||
$$ select edit_expense('b57b980b-247b-4be4-a0b7-03a7819c53ae', 'pending', '2023-05-07', 12, 'INV22', '3.33', '{4,3}', array['tag1', 'tag3']) $$,
|
||||
'Should be able to edit the second expense'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select invoice_number, invoice_date, contact_id, amount, tags from expense $$,
|
||||
$$ values ('INV11', '2023-05-06'::date, 13, 112, '{tag1}'::tag_name[])
|
||||
, ('INV22', '2023-05-07'::date, 12, 333, '{tag1,tag3}'::tag_name[])
|
||||
$$ select invoice_number, invoice_date, contact_id, amount, expense_status, tags from expense $$,
|
||||
$$ values ('INV11', '2023-05-06'::date, 13, 112, 'paid', '{tag1}'::tag_name[])
|
||||
, ('INV22', '2023-05-07'::date, 12, 333, 'pending', '{tag1,tag3}'::tag_name[])
|
||||
$$,
|
||||
'Should have updated all expenses'
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ reset client_min_messages;
|
|||
|
||||
begin;
|
||||
|
||||
select plan(67);
|
||||
select plan(74);
|
||||
|
||||
set search_path to numerus, auth, public;
|
||||
|
||||
|
@ -65,6 +65,14 @@ select col_type_is('expense', 'amount', 'integer');
|
|||
select col_not_null('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 col_is_fk('expense', 'currency_code');
|
||||
select fk_ok('expense', 'currency_code', 'currency', 'currency_code');
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
-- 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;
|
|
@ -0,0 +1,44 @@
|
|||
-- 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;
|
||||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
begin;
|
||||
|
||||
select has_function_privilege('numerus.add_expense(integer, date, integer, text, text, integer[], numerus.tag_name[])', 'execute');
|
||||
select has_function_privilege('numerus.add_expense(integer, text, date, integer, text, text, integer[], numerus.tag_name[])', 'execute');
|
||||
|
||||
rollback;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- 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;
|
|
@ -0,0 +1,15 @@
|
|||
-- 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;
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
begin;
|
||||
|
||||
select has_function_privilege('numerus.edit_expense(uuid, date, integer, text, text, integer[], numerus.tag_name[])', 'execute');
|
||||
select has_function_privilege('numerus.edit_expense(uuid, text, date, integer, text, text, integer[], numerus.tag_name[])', 'execute');
|
||||
|
||||
rollback;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- 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;
|
|
@ -0,0 +1,10 @@
|
|||
-- Verify numerus:expense_expense_status on pg
|
||||
|
||||
begin;
|
||||
|
||||
select expense_status
|
||||
from numerus.expense
|
||||
where false
|
||||
;
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,10 @@
|
|||
-- Verify numerus:expense_status on pg
|
||||
|
||||
begin;
|
||||
|
||||
select expense_status
|
||||
, name
|
||||
from numerus.expense_status
|
||||
where false;
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,11 @@
|
|||
-- Verify numerus:expense_status_i18n on pg
|
||||
|
||||
begin;
|
||||
|
||||
select expense_status
|
||||
, lang_tag
|
||||
, name
|
||||
from numerus.expense_status_i18n
|
||||
where false;
|
||||
|
||||
rollback;
|
|
@ -614,16 +614,19 @@ main > nav {
|
|||
}
|
||||
|
||||
.quote-status,
|
||||
.expense-status,
|
||||
.invoice-status {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.quote-status summary,
|
||||
.expense-status summary,
|
||||
.invoice-status summary {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.quote-status ul,
|
||||
.expense-status ul,
|
||||
.invoice-status ul {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -635,12 +638,14 @@ main > nav {
|
|||
}
|
||||
|
||||
.quote-status button,
|
||||
.expense-status button,
|
||||
.invoice-status button {
|
||||
border: 0;
|
||||
min-width: 15rem;
|
||||
}
|
||||
|
||||
[class^='quote-status-'],
|
||||
[class^='expense-status-'],
|
||||
[class^='invoice-status-'] {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
|
@ -658,11 +663,13 @@ main > nav {
|
|||