Compare commits

..

No commits in common. "ca0298213e9b2c292b6eca7c0d1dc7b0847999fd" and "5717a5b9ed9495377c2ebc3752eda7d09210c1a4" have entirely different histories.

9 changed files with 88 additions and 52 deletions

View File

@ -61,12 +61,12 @@ select add_product(1, 'Teia', 'Fusta resinosa de pi i daltres arbres, provine
alter sequence invoice_invoice_id_seq restart;
alter sequence invoice_product_invoice_product_id_seq restart;
select add_invoice(1, (current_date - '28 days'::interval)::date, 6, 'Vol esmorzar!', 1, '{producte}','{"(1,Teia,\"Fusta resinosa de pi i daltres arbres, provinent sobretot del cor de larbre, que crema amb molta facilitat.\",7.00,1,0.0,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
select add_invoice(1, (current_date - '24 days'::interval)::date, 5, '', 1, '{producte,bestia}','{"(1,Palla,Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.,25.00,25,0.0,{3})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
select add_invoice(1, (current_date - '17 days'::interval)::date, 4, '', 1, '{producte,higiene}','{"(1,\"Paper higiènic (pack de 32 U)\",Paper que susa per mantenir la higiene personal després de defecar o orinar.,7.99,10,0.0,{4})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
select add_invoice(1, (current_date - '7 days'::interval)::date, 3, '', 1, '{producte,mag}','{"(1,Mirra,Goma resinosa aromàtica de color gris groguenc i gust amargant.,7.22,144,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.45,1,0.0,{2})"}');
select add_invoice(1, (current_date - '4 days'::interval)::date, 2, '', 1, '{producte,mag}','{"(1,Encens,Goma resina fragrant que desprèn una olor característica quan es crema.,2.26,460,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.53,1,0.0,{2})"}');
select add_invoice(1, (current_date - '1 days'::interval)::date, 1, '', 1, '{producte,mag}','{"(1,Or,\"Metall de transició tou, brillant, groc, pesant, mal·leable, dúctil i que no reacciona amb la majoria de productes químics, però és sensible al clor i a laigua règia.\",57.82,18,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.43,1,0.0,{2})"}');
select add_invoice(1, '', (current_date - '28 days'::interval)::date, 6, 'Vol esmorzar!', 1, '{producte}','{"(1,Teia,\"Fusta resinosa de pi i daltres arbres, provinent sobretot del cor de larbre, que crema amb molta facilitat.\",7.00,1,0.0,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
select add_invoice(1, '', (current_date - '24 days'::interval)::date, 5, '', 1, '{producte,bestia}','{"(1,Palla,Tija seca dels cereals després que el gra o llavor ha estat separat mitjançant la trilla.,25.00,25,0.0,{3})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
select add_invoice(1, '', (current_date - '17 days'::interval)::date, 4, '', 1, '{producte,higiene}','{"(1,\"Paper higiènic (pack de 32 U)\",Paper que susa per mantenir la higiene personal després de defecar o orinar.,7.99,10,0.0,{4})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.64,1,0.0,{2})"}');
select add_invoice(1, '', (current_date - '7 days'::interval)::date, 3, '', 1, '{producte,mag}','{"(1,Mirra,Goma resinosa aromàtica de color gris groguenc i gust amargant.,7.22,144,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.45,1,0.0,{2})"}');
select add_invoice(1, '', (current_date - '4 days'::interval)::date, 2, '', 1, '{producte,mag}','{"(1,Encens,Goma resina fragrant que desprèn una olor característica quan es crema.,2.26,460,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",4.53,1,0.0,{2})"}');
select add_invoice(1, '', (current_date - '1 days'::interval)::date, 1, '', 1, '{producte,mag}','{"(1,Or,\"Metall de transició tou, brillant, groc, pesant, mal·leable, dúctil i que no reacciona amb la majoria de productes químics, però és sensible al clor i a laigua règia.\",57.82,18,0.05,{2})","(5,Cavall Fort,\"Revista quinzenal en llengua catalana i de còmic en català, destinada a infants i joves.\",3.43,1,0.0,{2})"}');
update invoice set invoice_status = 'paid' where invoice_id in (1, 5);
update invoice set invoice_status = 'unpaid' where invoice_id = 3;

View File

@ -16,7 +16,7 @@ begin;
set search_path to numerus, public;
create or replace function add_invoice(company integer, invoice_date date, contact_id integer, notes text, payment_method_id integer, tags tag_name[], products new_invoice_product[]) returns uuid as
create or replace function add_invoice(company integer, invoice_number text, invoice_date date, contact_id integer, notes text, payment_method_id integer, tags tag_name[], products new_invoice_product[]) returns uuid as
$$
declare
iid integer;
@ -25,9 +25,13 @@ declare
ccode text;
ipid integer;
begin
if invoice_number is null or length(trim(invoice_number)) = 0 then
invoice_number = next_invoice_number(company, invoice_date);
end if;
insert into invoice (company_id, invoice_number, invoice_date, contact_id, notes, currency_code, payment_method_id)
select company_id
, next_invoice_number(add_invoice.company, invoice_date)
, invoice_number
, invoice_date
, contact_id
, notes
@ -66,8 +70,8 @@ end;
$$
language plpgsql;
revoke execute on function add_invoice(integer, date, integer, text, integer, tag_name[], new_invoice_product[]) from public;
grant execute on function add_invoice(integer, date, integer, text, integer, tag_name[], new_invoice_product[]) to invoicer;
grant execute on function add_invoice(integer, date, integer, text, integer, tag_name[], new_invoice_product[]) to admin;
revoke execute on function add_invoice(integer, text, date, integer, text, integer, tag_name[], new_invoice_product[]) from public;
grant execute on function add_invoice(integer, text, date, integer, text, integer, tag_name[], new_invoice_product[]) to invoicer;
grant execute on function add_invoice(integer, text, date, integer, text, integer, tag_name[], new_invoice_product[]) to admin;
commit;

View File

@ -217,6 +217,7 @@ func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Para
if invoiceToDuplicate := r.URL.Query().Get("duplicate"); invoiceToDuplicate != "" {
form.MustFillFromDatabase(r.Context(), conn, invoiceToDuplicate)
form.InvoiceStatus.Selected = []string{"created"}
form.Number.Val = ""
}
form.Date.Val = time.Now().Format("2006-01-02")
w.WriteHeader(http.StatusOK)
@ -336,7 +337,7 @@ func mustGetInvoice(ctx context.Context, conn *Conn, company *Company, slug stri
if err := conn.QueryRow(ctx, "select array_agg(array[name, to_price(amount, $2)]) from invoice_tax_amount join tax using (tax_id) where invoice_id = $1", invoiceId, decimalDigits).Scan(&inv.Taxes); err != nil {
panic(err)
}
rows := conn.MustQuery(ctx, "select invoice_product.name, description, to_price(price, $2), (discount_rate * 100)::integer, quantity, to_price(subtotal, $2), to_price(total, $2), array_agg(array[tax_class.name, (tax_rate * 100)::integer::text]) filter (where tax_rate is not null) from invoice_product join invoice_product_amount using (invoice_product_id) left join invoice_product_tax using (invoice_product_id) left join tax using (tax_id) left join tax_class using (tax_class_id) where invoice_id = $1 group by invoice_product.name, description, discount_rate, price, quantity, subtotal, total", invoiceId, decimalDigits)
rows := conn.MustQuery(ctx, "select invoice_product.name, description, to_price(price, $2), (discount_rate * 100)::integer, quantity, to_price(subtotal, $2), to_price(total, $2), array_agg(array[tax_class.name, (tax_rate * 100)::integer::text]) from invoice_product join invoice_product_amount using (invoice_product_id) left join invoice_product_tax using (invoice_product_id) left join tax using (tax_id) left join tax_class using (tax_class_id) where invoice_id = $1 group by invoice_product.name, description, discount_rate, price, quantity, subtotal, total", invoiceId, decimalDigits)
defer rows.Close()
taxClasses := map[string]bool{}
for rows.Next() {
@ -455,7 +456,7 @@ func HandleAddInvoice(w http.ResponseWriter, r *http.Request, _ httprouter.Param
mustRenderNewInvoiceForm(w, r, form)
return
}
slug := conn.MustGetText(r.Context(), "", "select add_invoice($1, $2, $3, $4, $5, $6, $7)", company.Id, form.Date, form.Customer, form.Notes, form.PaymentMethod, form.Tags, NewInvoiceProductArray(form.Products))
slug := conn.MustGetText(r.Context(), "", "select add_invoice($1, $2, $3, $4, $5, $6, $7, $8)", company.Id, form.Number, form.Date, form.Customer, form.Notes, form.PaymentMethod, form.Tags, NewInvoiceProductArray(form.Products))
if IsHTMxRequest(r) {
w.Header().Set("HX-Trigger", "closeModal")
w.Header().Set("HX-Refresh", "true")
@ -528,9 +529,9 @@ func mustWriteInvoicesPdf(r *http.Request, slugs []string) []byte {
type invoiceForm struct {
locale *Locale
company *Company
Number string
InvoiceStatus *SelectField
Customer *SelectField
Number *InputField
Date *InputField
Notes *InputField
PaymentMethod *SelectField
@ -555,6 +556,11 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
Required: true,
Options: MustGetOptions(ctx, conn, "select contact_id::text, business_name from contact where company_id = $1 order by business_name", company.Id),
},
Number: &InputField{
Name: "number",
Label: pgettext("input", "Number", locale),
Type: "text",
},
Date: &InputField{
Name: "date",
Label: pgettext("input", "Invoice Date", locale),
@ -586,6 +592,7 @@ func (form *invoiceForm) Parse(r *http.Request) error {
}
form.InvoiceStatus.FillValue(r)
form.Customer.FillValue(r)
form.Number.FillValue(r)
form.Date.FillValue(r)
form.Notes.FillValue(r)
form.Tags.FillValue(r)
@ -682,7 +689,7 @@ func (form *invoiceForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s
, invoice_date
, notes
, payment_method_id
`, slug).Scan(&invoiceId, form.InvoiceStatus, form.Customer, &form.Number, form.Date, form.Notes, form.PaymentMethod, form.Tags)) {
`, slug).Scan(&invoiceId, form.InvoiceStatus, form.Customer, form.Number, form.Date, form.Notes, form.PaymentMethod, form.Tags)) {
form.PaymentMethod.Selected = selectedPaymentMethod
form.InvoiceStatus.Selected = selectedInvoiceStatus
return false
@ -883,7 +890,7 @@ func newEditInvoicePage(slug string, form *invoiceForm, r *http.Request) *editIn
return &editInvoicePage{
newNewInvoicePage(form, r),
slug,
form.Number,
form.Number.String(),
}
}

View File

@ -2,6 +2,6 @@
begin;
drop function if exists numerus.add_invoice(integer, date, integer, text, integer, numerus.tag_name[], numerus.new_invoice_product[]);
drop function if exists numerus.add_invoice(integer, text, date, integer, text, integer, numerus.tag_name[], numerus.new_invoice_product[]);
commit;

View File

@ -5,19 +5,19 @@ reset client_min_messages;
begin;
select plan(17);
select plan(19);
set search_path to auth, numerus, public;
select has_function('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]']);
select function_lang_is('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'plpgsql');
select function_returns('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'uuid');
select isnt_definer('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]']);
select volatility_is('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'volatile');
select function_privs_are('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'guest', array []::text[]);
select function_privs_are('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'invoicer', array ['EXECUTE']);
select function_privs_are('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'admin', array ['EXECUTE']);
select function_privs_are('numerus', 'add_invoice', array ['integer', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'authenticator', array []::text[]);
select has_function('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]']);
select function_lang_is('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'plpgsql');
select function_returns('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'uuid');
select isnt_definer('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]']);
select volatility_is('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'volatile');
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'guest', array []::text[]);
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'invoicer', array ['EXECUTE']);
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'admin', array ['EXECUTE']);
select function_privs_are('numerus', 'add_invoice', array ['integer', 'text', 'date', 'integer', 'text', 'integer', 'tag_name[]', 'new_invoice_product[]'], 'authenticator', array []::text[]);
set client_min_messages to warning;
@ -83,45 +83,59 @@ values (12, 1, 'Contact 2.1', 'XX555', '', '777-777-777', 'c@c', '', '', '', '',
select lives_ok(
$$ select add_invoice(1, '2023-02-15', 12, 'Notes 1', 111, '{tag1,tag2}','{"(7,Product 1,Description 1,12.24,2,0.0,{4})"}') $$,
$$ select add_invoice(1, 'INV001', '2023-02-15', 12, 'Notes 1', 111, '{tag1,tag2}','{"(7,Product 1,Description 1,12.24,2,0.0,{4})"}') $$,
'Should be able to insert an invoice for the first company with a product'
);
select lives_ok(
$$ select add_invoice(1, '2023-02-16', 13, 'Notes 2', 111, '{}', '{"(7,Product 1 bis,Description 1 bis,33.33,1,0.50,\"{4,3}\")","(8,Product 2,Description 2,24.00,3,0.75,{})"}') $$,
$$ select add_invoice(1, 'INV002', '2023-02-16', 13, 'Notes 2', 111, '{}', '{"(7,Product 1 bis,Description 1 bis,33.33,1,0.50,\"{4,3}\")","(8,Product 2,Description 2,24.00,3,0.75,{})"}') $$,
'Should be able to insert a second invoice for the first company with two product'
);
select lives_ok(
$$ select add_invoice(2, '2023-02-14', 15, 'Notes 3', 222, '{tag3}','{"(11,Product 4.3,,11.11,1,0.0,{6})"}') $$,
$$ select add_invoice(2, 'INV101', '2023-02-14', 15, 'Notes 3', 222, '{tag3}','{"(11,Product 4.3,,11.11,1,0.0,{6})"}') $$,
'Should be able to insert an invoice for the second company with a product'
);
select lives_ok(
$$ select add_invoice(1, NULL, '2023-03-15', 13, '', 111, '{tag2}', '{"(7,PA1,DA1,44.33,1,0.50,{})"}') $$,
'Should be able to insert an invoice with an autogenerated number'
);
select lives_ok(
$$ select add_invoice(2, ' ', '2023-04-16', 14, '', 222, '{tag2,tag3,tag4}','{"(11,PA2,DA2,55.33,10,0.75,{})"}') $$,
'Should consider non-null, but otherwise empty numbers the same as null and autogenerate it'
);
select bag_eq(
$$ select company_id, invoice_number, invoice_date, contact_id, invoice_status, notes, payment_method_id, currency_code, created_at from invoice $$,
$$ values (1, 'F20230006', '2023-02-15'::date, 12, 'created', 'Notes 1', 111, 'EUR', current_timestamp)
, (1, 'F20230007', '2023-02-16'::date, 13, 'created', 'Notes 2', 111, 'EUR', current_timestamp)
, (2, 'INV056-23', '2023-02-14'::date, 15, 'created', 'Notes 3', 222, 'USD', current_timestamp)
$$ values (1, 'INV001', '2023-02-15'::date, 12, 'created', 'Notes 1', 111, 'EUR', current_timestamp)
, (1, 'INV002', '2023-02-16'::date, 13, 'created', 'Notes 2', 111, 'EUR', current_timestamp)
, (2, 'INV101', '2023-02-14'::date, 15, 'created', 'Notes 3', 222, 'USD', current_timestamp)
, (1, 'F20230006', '2023-03-15'::date, 13, 'created', '', 111, 'EUR', current_timestamp)
, (2, 'INV056-23', '2023-04-16'::date, 14, 'created', '', 222, 'USD', current_timestamp)
$$,
'Should have created all invoices'
);
select bag_eq(
$$ select invoice_number, product_id, name, description, price, quantity, discount_rate from invoice_product join invoice using (invoice_id) $$,
$$ values ('F20230006', 7, 'Product 1', 'Description 1', 1224, 2, 0.00)
, ('F20230007', 7, 'Product 1 bis', 'Description 1 bis', 3333, 1, 0.50)
, ('F20230007', 8, 'Product 2', 'Description 2', 2400, 3, 0.75)
, ('INV056-23', 11, 'Product 4.3', '', 1111, 1, 0.0)
$$ values ('INV001', 7, 'Product 1', 'Description 1', 1224, 2, 0.00)
, ('INV002', 7, 'Product 1 bis', 'Description 1 bis', 3333, 1, 0.50)
, ('INV002', 8, 'Product 2', 'Description 2', 2400, 3, 0.75)
, ('INV101', 11, 'Product 4.3', '', 1111, 1, 0.0)
, ('F20230006', 7, 'PA1', 'DA1', 4433, 1, 0.50)
, ('INV056-23', 11, 'PA2', 'DA2', 5533, 10, 0.75)
$$,
'Should have created all invoice products'
);
select bag_eq(
$$ select invoice_number, product_id, tax_id, tax_rate from invoice_product_tax join invoice_product using (invoice_product_id) join invoice using (invoice_id) $$,
$$ values ('F20230006', 7, 4, 0.21)
, ('F20230007', 7, 4, 0.21)
, ('F20230007', 7, 3, -0.15)
, ('INV056-23', 11, 6, 0.10)
$$ values ('INV001', 7, 4, 0.21)
, ('INV002', 7, 4, 0.21)
, ('INV002', 7, 3, -0.15)
, ('INV101', 11, 6, 0.10)
$$,
'Should have created all invoice product taxes'
);
@ -130,16 +144,22 @@ select bag_eq(
$$ select company_id, name from tag $$,
$$ values (1, 'tag1')
, (1, 'tag2')
, (2, 'tag2')
, (2, 'tag3')
, (2, 'tag4')
$$,
'Should have added all new tags once'
);
select bag_eq(
$$ select invoice_number, tag.name from invoice_tag join invoice using (invoice_id) join tag using (tag_id) $$,
$$ values ('F20230006', 'tag1')
$$ values ('INV001', 'tag1')
, ('INV001', 'tag2')
, ('INV101', 'tag3')
, ('F20230006', 'tag2')
, ('INV056-23', 'tag2')
, ('INV056-23', 'tag3')
, ('INV056-23', 'tag4')
$$,
'Should have assigned the tags to invoices'
);

View File

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

View File

@ -22,6 +22,7 @@
{{ with .Form -}}
{{ template "select-field" .Customer }}
{{ template "hidden-field" .Number }}
{{ template "hidden-field" .Date }}
{{ template "tags-field" .Tags }}
{{ template "select-field" .PaymentMethod }}

View File

@ -23,6 +23,7 @@
{{ with .Form -}}
{{ template "hidden-select-field" .InvoiceStatus }}
{{ template "select-field" .Customer }}
{{ template "input-field" .Number }}
{{ template "input-field" .Date }}
{{ template "tags-field" .Tags }}
{{ template "select-field" .PaymentMethod }}

View File

@ -8,7 +8,7 @@
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a href="{{ companyURI "/invoices"}}">{{( pgettext "Invoices" "title" )}}</a> /
{{ if eq .Form.Number "" }}
{{ if eq .Form.Number.Val "" }}
<a>{{( pgettext "New Invoice" "title" )}}</a>
{{ else }}
<a>{{ .Form.Number }}</a>
@ -26,19 +26,22 @@
{{- with .Form }}
{{ template "hidden-select-field" .Customer }}
{{ template "hidden-field" .Number }}
{{ template "hidden-field" .Date }}
{{ template "hidden-field" .Notes }}
{{ template "hidden-field" .Tags }}
{{- range $product := .Products }}
{{ template "hidden-field" .InvoiceProductId }}
{{ template "hidden-field" .ProductId }}
{{ template "hidden-field" .Name }}
{{ template "hidden-field" .Description }}
{{ template "hidden-field" .Price }}
{{ template "hidden-field" .Quantity }}
{{ template "hidden-field" .Discount }}
{{ template "hidden-select-field" .Tax }}
<fieldset>
{{ template "hidden-field" .InvoiceProductId }}
{{ template "hidden-field" .ProductId }}
{{ template "hidden-field" .Name }}
{{ template "hidden-field" .Description }}
{{ template "hidden-field" .Price }}
{{ template "hidden-field" .Quantity }}
{{ template "hidden-field" .Discount }}
{{ template "hidden-select-field" .Tax }}
</fieldset>
{{- end }}
{{- end }}