Compare commits
3 Commits
1164210d84
...
b48a974086
Author | SHA1 | Date |
---|---|---|
jordi fita mas | b48a974086 | |
jordi fita mas | b7578a56df | |
jordi fita mas | fa97f53dd7 |
|
@ -8,24 +8,27 @@
|
||||||
-- 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, 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
|
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, tags)
|
insert into expense (company_id, contact_id, invoice_number, invoice_date, amount, currency_code, expense_status, 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)
|
||||||
|
@ -43,8 +46,10 @@ end;
|
||||||
$$
|
$$
|
||||||
language plpgsql;
|
language plpgsql;
|
||||||
|
|
||||||
revoke execute on function add_expense(integer, date, integer, text, text, integer[], tag_name[]) from public;
|
revoke execute on function add_expense(integer, text, 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, 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 admin;
|
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;
|
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: 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, 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
|
declare
|
||||||
eid integer;
|
eid integer;
|
||||||
|
@ -20,6 +22,7 @@ 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
|
||||||
|
@ -43,8 +46,8 @@ end;
|
||||||
$$
|
$$
|
||||||
language plpgsql;
|
language plpgsql;
|
||||||
|
|
||||||
revoke execute on function edit_expense(uuid, date, integer, text, text, integer[], tag_name[]) from public;
|
revoke execute on function edit_expense(uuid, text, 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, 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 admin;
|
grant execute on function edit_expense(uuid, text, date, integer, text, text, integer[], tag_name[]) to admin;
|
||||||
|
|
||||||
commit;
|
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;
|
|
@ -20,12 +20,15 @@ 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) {
|
||||||
|
@ -38,15 +41,16 @@ func IndexExpenses(w http.ResponseWriter, r *http.Request, _ httprouter.Params)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
page := &expensesIndexPage{
|
page := &expensesIndexPage{
|
||||||
Expenses: mustCollectExpenseEntries(r.Context(), conn, filters),
|
Expenses: mustCollectExpenseEntries(r.Context(), conn, locale, 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, filters *expenseFilterForm) []*ExpenseEntry {
|
func mustCollectExpenseEntries(ctx context.Context, conn *Conn, locale *Locale, filters *expenseFilterForm) []*ExpenseEntry {
|
||||||
where, args := filters.BuildQuery(nil)
|
where, args := filters.BuildQuery([]interface{}{locale.Language.String()})
|
||||||
rows := conn.MustQuery(ctx, fmt.Sprintf(`
|
rows := conn.MustQuery(ctx, fmt.Sprintf(`
|
||||||
select expense.slug
|
select expense.slug
|
||||||
, invoice_date
|
, invoice_date
|
||||||
|
@ -55,9 +59,12 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, filters *expense
|
||||||
, 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
|
||||||
|
@ -67,7 +74,7 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, filters *expense
|
||||||
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); 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)
|
panic(err)
|
||||||
}
|
}
|
||||||
entries = append(entries, entry)
|
entries = append(entries, entry)
|
||||||
|
@ -79,6 +86,31 @@ func mustCollectExpenseEntries(ctx context.Context, conn *Conn, filters *expense
|
||||||
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(`
|
||||||
|
@ -138,6 +170,7 @@ type expenseForm struct {
|
||||||
Tax *SelectField
|
Tax *SelectField
|
||||||
Amount *InputField
|
Amount *InputField
|
||||||
File *FileField
|
File *FileField
|
||||||
|
ExpenseStatus *SelectField
|
||||||
Tags *TagsField
|
Tags *TagsField
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,6 +216,13 @@ 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),
|
||||||
|
@ -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 {
|
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
|
||||||
|
@ -202,6 +252,7 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -217,16 +268,20 @@ 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 {
|
||||||
return !notFoundErrorOrPanic(conn.QueryRow(ctx, `
|
selectedExpenseStatus := form.ExpenseStatus.Selected
|
||||||
|
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)
|
||||||
|
@ -237,6 +292,7 @@ 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,
|
||||||
|
@ -244,7 +300,12 @@ func (form *expenseForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s
|
||||||
form.InvoiceDate,
|
form.InvoiceDate,
|
||||||
form.Amount,
|
form.Amount,
|
||||||
form.Tax,
|
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) {
|
func HandleAddExpense(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
|
@ -267,7 +328,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)", 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 {
|
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)
|
||||||
}
|
}
|
||||||
|
@ -287,6 +348,13 @@ 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) {
|
||||||
|
@ -296,7 +364,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)", slug, 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, $8)", slug, form.ExpenseStatus, form.InvoiceDate, form.Invoicer, form.InvoiceNumber, form.Amount, taxes, form.Tags); found == "" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -305,6 +373,7 @@ 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
|
||||||
|
@ -313,6 +382,7 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -346,6 +416,12 @@ 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),
|
||||||
|
@ -372,6 +448,7 @@ 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
|
||||||
|
@ -398,6 +475,7 @@ 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)
|
||||||
|
|
|
@ -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: 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()),
|
Options: mustGetInvoiceStatusOptions(ctx, conn, locale),
|
||||||
},
|
},
|
||||||
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: 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()),
|
Options: mustGetInvoiceStatusOptions(ctx, conn, locale),
|
||||||
},
|
},
|
||||||
Customer: &SelectField{
|
Customer: &SelectField{
|
||||||
Name: "customer",
|
Name: "customer",
|
||||||
|
@ -724,6 +724,16 @@ 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
|
||||||
|
|
|
@ -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;
|
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;
|
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;
|
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;
|
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
|
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
|
||||||
|
|
|
@ -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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
|
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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'plpgsql');
|
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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'uuid');
|
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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
|
select isnt_definer('numerus', 'add_expense', array ['integer', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
|
||||||
select volatility_is('numerus', 'add_expense', array ['integer', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'volatile');
|
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', '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[]'], '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', '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[]'], 'admin', 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[]'], 'authenticator', array []::text[]);
|
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;
|
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, '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'
|
'Should be able to insert an expense for the first company with a tax'
|
||||||
);
|
);
|
||||||
|
|
||||||
select lives_ok(
|
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'
|
'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, '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'
|
'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, tags, created_at from expense $$,
|
$$ 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', '{tag1,tag2}'::tag_name[], current_timestamp)
|
$$ 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', '{}'::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', '{tag3}'::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'
|
'Should have created all expenses'
|
||||||
);
|
);
|
||||||
|
|
|
@ -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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
|
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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'plpgsql');
|
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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'uuid');
|
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', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
|
select isnt_definer('numerus', 'edit_expense', array ['uuid', 'text', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]']);
|
||||||
select volatility_is('numerus', 'edit_expense', array ['uuid', 'date', 'integer', 'text', 'text', 'integer[]', 'tag_name[]'], 'volatile');
|
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', '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[]'], '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', '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[]'], 'admin', 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[]'], 'authenticator', array []::text[]);
|
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;
|
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, tags)
|
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', '{tag1}')
|
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', '{tag2}')
|
, (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)
|
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', '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'
|
'Should be able to edit the first expense'
|
||||||
);
|
);
|
||||||
|
|
||||||
select lives_ok(
|
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'
|
'Should be able to edit the second expense'
|
||||||
);
|
);
|
||||||
|
|
||||||
select bag_eq(
|
select bag_eq(
|
||||||
$$ select invoice_number, invoice_date, contact_id, amount, tags from expense $$,
|
$$ select invoice_number, invoice_date, contact_id, amount, expense_status, tags from expense $$,
|
||||||
$$ values ('INV11', '2023-05-06'::date, 13, 112, '{tag1}'::tag_name[])
|
$$ values ('INV11', '2023-05-06'::date, 13, 112, 'paid', '{tag1}'::tag_name[])
|
||||||
, ('INV22', '2023-05-07'::date, 12, 333, '{tag1,tag3}'::tag_name[])
|
, ('INV22', '2023-05-07'::date, 12, 333, 'pending', '{tag1,tag3}'::tag_name[])
|
||||||
$$,
|
$$,
|
||||||
'Should have updated all expenses'
|
'Should have updated all expenses'
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,7 @@ reset client_min_messages;
|
||||||
|
|
||||||
begin;
|
begin;
|
||||||
|
|
||||||
select plan(67);
|
select plan(74);
|
||||||
|
|
||||||
set search_path to numerus, auth, public;
|
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_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');
|
||||||
|
|
|
@ -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;
|
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;
|
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;
|
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;
|
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;
|
|
@ -194,6 +194,10 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -610,16 +614,19 @@ 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;
|
||||||
|
@ -631,12 +638,14 @@ 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;
|
||||||
|
@ -654,11 +663,13 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -699,11 +710,6 @@ 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;
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
{{- /*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 }}" data-hx-boost="true">
|
<form enctype="multipart/form-data" method="POST" action="{{ companyURI "/expenses/" }}{{ .Slug }}"
|
||||||
|
data-hx-boost="true">
|
||||||
{{ csrfToken }}
|
{{ csrfToken }}
|
||||||
{{ putMethod }}
|
{{ putMethod }}
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
{{ 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 }}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
>
|
>
|
||||||
{{ 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 }}
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
<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>
|
||||||
|
@ -50,11 +52,33 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ with .Expenses }}
|
{{ with .Expenses }}
|
||||||
{{- range . }}
|
{{- range $expense := . }}
|
||||||
<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"
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
{{ 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>
|
||||||
|
|
Loading…
Reference in New Issue