Compare commits

..

No commits in common. "8efae0485e9e72c844bff6594d8160250e419621" and "dfdc9fde76a03c32bdca80b2b30cf6d30086629f" have entirely different histories.

14 changed files with 184 additions and 533 deletions

View File

@ -20,7 +20,6 @@ $$
declare
iid integer;
products_to_keep integer[];
products_to_delete integer[];
company integer;
ccode text;
product edited_invoice_product;
@ -79,16 +78,7 @@ begin
join unnest(product.tax) as ptax(tax_id) using (tax_id);
end loop;
select array_agg(invoice_product_id)
into products_to_delete
from invoice_product
where invoice_id = iid
and not (invoice_product_id = any(products_to_keep));
if array_length(products_to_delete, 1) > 0 then
delete from invoice_product_tax where invoice_product_id = any(products_to_delete);
delete from invoice_product where invoice_product_id = any(products_to_delete);
end if;
delete from invoice_product where invoice_id = iid and not (invoice_product_id = any(products_to_keep));
delete from invoice_tag where invoice_id = iid;

View File

@ -35,18 +35,7 @@ func (field *InputField) Scan(value interface{}) error {
field.Val = ""
return nil
}
switch v := value.(type) {
case time.Time:
if field.Type == "date" {
field.Val = v.Format("2006-01-02")
} else if field.Type == "time" {
field.Val = v.Format("15:04")
} else {
field.Val = v.Format(time.RFC3339)
}
default:
field.Val = fmt.Sprintf("%v", v)
}
field.Val = fmt.Sprintf("%v", value)
return nil
}
@ -74,10 +63,6 @@ func (field *InputField) Float64() float64 {
return value
}
func (field *InputField) String() string {
return field.Val
}
type SelectOption struct {
Value string
Label string

View File

@ -117,15 +117,14 @@ func mustCollectInvoiceStatuses(ctx context.Context, conn *Conn, locale *Locale)
}
func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
locale := getLocale(r)
conn := getConn(r)
company := mustGetCompany(r)
form := newInvoiceForm(r.Context(), conn, locale, company)
slug := params[0].Value
if slug == "new" {
locale := getLocale(r)
form := newInvoiceForm(r.Context(), conn, locale, company)
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")
@ -305,11 +304,10 @@ func mustRenderNewInvoiceForm(w http.ResponseWriter, r *http.Request, form *invo
mustRenderAppTemplate(w, r, "invoices/new.gohtml", page)
}
func mustRenderNewInvoiceProductsForm(w http.ResponseWriter, r *http.Request, action string, form *invoiceForm) {
func mustRenderNewInvoiceProductsForm(w http.ResponseWriter, r *http.Request, form *invoiceForm) {
conn := getConn(r)
company := mustGetCompany(r)
page := newInvoiceProductsPage{
Action: companyURI(company, action),
Form: form,
Products: mustGetProductChoices(r.Context(), conn, company),
}
@ -336,7 +334,6 @@ func mustGetProductChoices(ctx context.Context, conn *Conn, company *Company) []
}
type newInvoiceProductsPage struct {
Action string
Form *invoiceForm
Products []*productChoice
}
@ -365,18 +362,39 @@ 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, $8)", company.Id, form.Number, form.Date, form.Customer, form.Notes, form.PaymentMethod, form.SplitTags(), NewInvoiceProductArray(form.Products))
reg := regexp.MustCompile("[^a-z0-9-]+")
tags := strings.Split(reg.ReplaceAllString(form.Tags.Val, " "), " ")
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, tags, NewInvoiceProductArray(form.Products))
http.Redirect(w, r, companyURI(company, "/invoices/"+slug), http.StatusSeeOther)
}
func HandleNewInvoiceAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
switch params[0].Value {
case "new":
handleInvoiceAction(w, r, "/invoices/new", mustRenderNewInvoiceForm)
case "batch":
HandleBatchInvoiceAction(w, r, params)
func HandleNewInvoiceAction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
locale := getLocale(r)
conn := getConn(r)
company := mustGetCompany(r)
form := newInvoiceForm(r.Context(), conn, locale, company)
if err := form.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := verifyCsrfTokenValid(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
switch r.Form.Get("action") {
case "update":
form.Update()
w.WriteHeader(http.StatusOK)
mustRenderNewInvoiceForm(w, r, form)
case "select-products":
w.WriteHeader(http.StatusOK)
mustRenderNewInvoiceProductsForm(w, r, form)
case "add-products":
form.AddProducts(r.Context(), conn, r.Form["id"])
w.WriteHeader(http.StatusOK)
mustRenderNewInvoiceForm(w, r, form)
default:
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
http.Error(w, gettext("Invalid action", locale), http.StatusBadRequest)
}
}
@ -432,7 +450,6 @@ func mustWriteInvoicesPdf(r *http.Request, slugs []string) []byte {
type invoiceForm struct {
locale *Locale
company *Company
InvoiceStatus *SelectField
Customer *SelectField
Number *InputField
Date *InputField
@ -446,13 +463,6 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
return &invoiceForm{
locale: locale,
company: company,
InvoiceStatus: &SelectField{
Name: "invoice_status",
Required: true,
Label: pgettext("input", "Invoice Status", locale),
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()),
},
Customer: &SelectField{
Name: "customer",
Label: pgettext("input", "Customer", locale),
@ -494,7 +504,6 @@ func (form *invoiceForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
form.InvoiceStatus.FillValue(r)
form.Customer.FillValue(r)
form.Number.FillValue(r)
form.Date.FillValue(r)
@ -520,8 +529,7 @@ func (form *invoiceForm) Parse(r *http.Request) error {
func (form *invoiceForm) Validate() bool {
validator := newFormValidator()
validator.CheckValidSelectOption(form.InvoiceStatus, gettext("Selected invoice status is not valid.", form.locale))
validator.CheckValidSelectOption(form.Customer, gettext("Selected customer is not valid.", form.locale))
validator.CheckValidSelectOption(form.Customer, gettext("Name can not be empty.", form.locale))
if validator.CheckRequiredInput(form.Date, gettext("Invoice date can not be empty.", form.locale)) {
validator.CheckValidDate(form.Date, gettext("Invoice date must be a valid date.", form.locale))
}
@ -548,7 +556,7 @@ func (form *invoiceForm) Update() {
}
func (form *invoiceForm) AddProducts(ctx context.Context, conn *Conn, productsId []string) {
form.mustAddProductsFromQuery(ctx, conn, "select '', product_id, name, description, to_price(price, decimal_digits), 1 as quantity, 0 as discount, array_remove(array_agg(tax_id), null) from product join company using (company_id) join currency using (currency_code) left join product_tax using (product_id) where product_id = any ($1) group by product_id, name, description, price, decimal_digits", productsId)
form.mustAddProductsFromQuery(ctx, conn, "select product_id, name, description, to_price(price, decimal_digits), 1 as quantity, 0 as discount, array_remove(array_agg(tax_id), null) from product join company using (company_id) join currency using (currency_code) left join product_tax using (product_id) where product_id = any ($1) group by product_id, name, description, price, decimal_digits", productsId)
}
func (form *invoiceForm) mustAddProductsFromQuery(ctx context.Context, conn *Conn, sql string, args ...interface{}) {
@ -558,7 +566,7 @@ func (form *invoiceForm) mustAddProductsFromQuery(ctx context.Context, conn *Con
defer rows.Close()
for rows.Next() {
product := newInvoiceProductForm(index, form.company, form.locale, taxOptions)
if err := rows.Scan(product.InvoiceProductId, product.ProductId, product.Name, product.Description, product.Price, product.Quantity, product.Discount, product.Tax); err != nil {
if err := rows.Scan(product.ProductId, product.Name, product.Description, product.Price, product.Quantity, product.Discount, product.Tax); err != nil {
panic(err)
}
form.Products = append(form.Products, product)
@ -569,15 +577,12 @@ func (form *invoiceForm) mustAddProductsFromQuery(ctx context.Context, conn *Con
}
}
func (form *invoiceForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) bool {
func (form *invoiceForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) {
var invoiceId int
selectedInvoiceStatus := form.InvoiceStatus.Selected
form.InvoiceStatus.Clear()
selectedPaymentMethod := form.PaymentMethod.Selected
form.PaymentMethod.Clear()
if notFoundErrorOrPanic(conn.QueryRow(ctx, `
select invoice_id
, invoice_status
, contact_id
, invoice_number
, invoice_date
@ -593,29 +598,21 @@ 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.Customer, form.Number, form.Date, form.Notes, form.PaymentMethod, form.Tags)) {
form.PaymentMethod.Selected = selectedPaymentMethod
form.InvoiceStatus.Selected = selectedInvoiceStatus
return false
return
}
form.Products = []*invoiceProductForm{}
form.mustAddProductsFromQuery(ctx, conn, "select invoice_product_id::text, product_id, name, description, to_price(price, $2), quantity, (discount_rate * 100)::integer, array_remove(array_agg(tax_id), null) from invoice_product left join invoice_product_tax using (invoice_product_id) where invoice_id = $1 group by invoice_product_id, product_id, name, description, discount_rate, price, quantity", invoiceId, form.company.DecimalDigits)
return true
form.mustAddProductsFromQuery(ctx, conn, "select product_id, name, description, to_price(price, $2), quantity, (discount_rate * 100)::integer, array_remove(array_agg(tax_id), null) from invoice_product left join invoice_product_tax using (invoice_product_id) where invoice_id = $1 group by product_id, name, description, discount_rate, price, quantity", invoiceId, form.company.DecimalDigits)
}
func mustGetTaxOptions(ctx context.Context, conn *Conn, company *Company) []*SelectOption {
return MustGetGroupedOptions(ctx, conn, "select tax_id::text, tax.name, tax_class.name from tax join tax_class using (tax_class_id) where tax.company_id = $1 order by tax_class.name, tax.name", company.Id)
}
func (form *invoiceForm) SplitTags() []string {
reg := regexp.MustCompile("[^a-z0-9-]+")
return strings.Split(reg.ReplaceAllString(form.Tags.Val, " "), " ")
}
type invoiceProductForm struct {
locale *Locale
company *Company
InvoiceProductId *InputField
ProductId *InputField
Name *InputField
Description *InputField
@ -629,11 +626,6 @@ func newInvoiceProductForm(index int, company *Company, locale *Locale, taxOptio
form := &invoiceProductForm{
locale: locale,
company: company,
InvoiceProductId: &InputField{
Label: pgettext("input", "Id", locale),
Type: "hidden",
Required: true,
},
ProductId: &InputField{
Label: pgettext("input", "Id", locale),
Type: "hidden",
@ -686,7 +678,6 @@ func newInvoiceProductForm(index int, company *Company, locale *Locale, taxOptio
func (form *invoiceProductForm) Reindex(index int) {
suffix := "." + strconv.Itoa(index)
form.InvoiceProductId.Name = "product.invoice_product_id" + suffix
form.ProductId.Name = "product.id" + suffix
form.Name.Name = "product.name" + suffix
form.Description.Name = "product.description" + suffix
@ -700,7 +691,6 @@ func (form *invoiceProductForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
form.InvoiceProductId.FillValue(r)
form.ProductId.FillValue(r)
form.Name.FillValue(r)
form.Description.FillValue(r)
@ -713,7 +703,6 @@ func (form *invoiceProductForm) Parse(r *http.Request) error {
func (form *invoiceProductForm) Validate() bool {
validator := newFormValidator()
validator.CheckRequiredInput(form.ProductId, gettext("Product ID can not be empty.", form.locale))
validator.CheckRequiredInput(form.Name, gettext("Name can not be empty.", form.locale))
if validator.CheckRequiredInput(form.Price, gettext("Price can not be empty.", form.locale)) {
validator.CheckValidDecimal(form.Price, form.company.MinCents(), math.MaxFloat64, gettext("Price must be a number greater than zero.", form.locale))
@ -730,11 +719,8 @@ func (form *invoiceProductForm) Validate() bool {
}
func HandleUpdateInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
locale := getLocale(r)
conn := getConn(r)
company := mustGetCompany(r)
form := newInvoiceForm(r.Context(), conn, locale, company)
if err := form.Parse(r); err != nil {
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
@ -742,97 +728,10 @@ func HandleUpdateInvoice(w http.ResponseWriter, r *http.Request, params httprout
http.Error(w, err.Error(), http.StatusForbidden)
return
}
if r.FormValue("quick") == "status" {
slug := conn.MustGetText(r.Context(), "", "update invoice set invoice_status = $1 where slug = $2 returning slug", form.InvoiceStatus, params[0].Value)
invoiceStatus := r.FormValue("status")
slug := conn.MustGetText(r.Context(), "", "update invoice set invoice_status = $1 where slug = $2 returning slug", invoiceStatus, params[0].Value)
if slug == "" {
http.NotFound(w, r)
}
http.Redirect(w, r, companyURI(mustGetCompany(r), "/invoices"), http.StatusSeeOther)
} else {
slug := params[0].Value
if !form.Validate() {
w.WriteHeader(http.StatusUnprocessableEntity)
mustRenderEditInvoiceForm(w, r, slug, form)
return
}
slug = conn.MustGetText(r.Context(), "", "select edit_invoice($1, $2, $3, $4, $5, $6, $7)", slug, form.InvoiceStatus, form.Customer, form.Notes, form.PaymentMethod, form.SplitTags(), EditedInvoiceProductArray(form.Products))
if slug == "" {
http.NotFound(w, r)
return
}
http.Redirect(w, r, companyURI(company, "/invoices/"+slug), http.StatusSeeOther)
}
}
func ServeEditInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
conn := getConn(r)
company := mustGetCompany(r)
slug := params[0].Value
locale := getLocale(r)
form := newInvoiceForm(r.Context(), conn, locale, company)
if !form.MustFillFromDatabase(r.Context(), conn, slug) {
http.NotFound(w, r)
return
}
w.WriteHeader(http.StatusOK)
mustRenderEditInvoiceForm(w, r, slug, form)
}
type editInvoicePage struct {
*newInvoicePage
Slug string
Number string
}
func newEditInvoicePage(slug string, form *invoiceForm, r *http.Request) *editInvoicePage {
return &editInvoicePage{
newNewInvoicePage(form, r),
slug,
form.Number.String(),
}
}
func mustRenderEditInvoiceForm(w http.ResponseWriter, r *http.Request, slug string, form *invoiceForm) {
page := newEditInvoicePage(slug, form, r)
mustRenderAppTemplate(w, r, "invoices/edit.gohtml", page)
}
func HandleEditInvoiceAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
slug := params[0].Value
actionUri := fmt.Sprintf("/invoices/%s/edit", slug)
handleInvoiceAction(w, r, actionUri, func(w http.ResponseWriter, r *http.Request, form *invoiceForm) {
mustRenderEditInvoiceForm(w, r, slug, form)
})
}
type renderFormFunc func(w http.ResponseWriter, r *http.Request, form *invoiceForm)
func handleInvoiceAction(w http.ResponseWriter, r *http.Request, action string, renderForm renderFormFunc) {
locale := getLocale(r)
conn := getConn(r)
company := mustGetCompany(r)
form := newInvoiceForm(r.Context(), conn, locale, company)
if err := form.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err := verifyCsrfTokenValid(r); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
switch r.Form.Get("action") {
case "update":
form.Update()
w.WriteHeader(http.StatusOK)
renderForm(w, r, form)
case "select-products":
w.WriteHeader(http.StatusOK)
mustRenderNewInvoiceProductsForm(w, r, action, form)
case "add-products":
form.AddProducts(r.Context(), conn, r.Form["id"])
w.WriteHeader(http.StatusOK)
renderForm(w, r, form)
default:
http.Error(w, gettext("Invalid action", locale), http.StatusBadRequest)
}
}

View File

@ -34,40 +34,6 @@ func (src NewInvoiceProductArray) EncodeBinary(ci *pgtype.ConnInfo, buf []byte)
return array.EncodeBinary(ci, buf)
}
type EditedInvoiceProductArray []*invoiceProductForm
func (src EditedInvoiceProductArray) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) {
typeName := "edited_invoice_product[]"
dt, ok := ci.DataTypeForName(typeName)
if !ok {
return nil, fmt.Errorf("unable to find oid for type name %v", typeName)
}
var values [][]interface{}
for _, form := range src {
var invoiceProductId interface{} = nil
if form.InvoiceProductId.Val != "" {
if id := form.InvoiceProductId.Integer(); id > 0 {
invoiceProductId = id
}
}
values = append(values, []interface{}{
invoiceProductId,
form.ProductId.Val,
form.Name.Val,
form.Description.Val,
form.Price.Val,
form.Quantity.Val,
form.Discount.Float64() / 100.0,
form.Tax.Selected,
})
}
array := pgtype.NewValue(dt.Value).(pgtype.ValueTranscoder)
if err := array.Set(values); err != nil {
return nil, err
}
return array.EncodeBinary(ci, buf)
}
func registerPgTypes(ctx context.Context, conn *pgx.Conn) error {
if _, err := conn.Exec(ctx, "set role to admin"); err != nil {
return err
@ -105,36 +71,6 @@ func registerPgTypes(ctx context.Context, conn *pgx.Conn) error {
return err
}
editedInvoiceProduct, err := pgtype.NewCompositeType(
"edited_invoice_product",
[]pgtype.CompositeTypeField{
{"invoice_product_id", pgtype.Int4OID},
{"product_id", pgtype.Int4OID},
{"name", pgtype.TextOID},
{"description", pgtype.TextOID},
{"price", pgtype.TextOID},
{"quantity", pgtype.Int4OID},
{"discount_rate", discountRateOID},
{"tax", pgtype.Int4ArrayOID},
},
conn.ConnInfo(),
)
if err != nil {
return err
}
editedInvoiceProductOID, err := registerPgType(ctx, conn, editedInvoiceProduct, editedInvoiceProduct.TypeName())
if err != nil {
return err
}
editedInvoiceProductArray := pgtype.NewArrayType("edited_invoice_product[]", editedInvoiceProductOID, func() pgtype.ValueTranscoder {
value := editedInvoiceProduct.NewTypeValue()
return value.(pgtype.ValueTranscoder)
})
_, err = registerPgType(ctx, conn, editedInvoiceProductArray, editedInvoiceProductArray.TypeName())
if err != nil {
return err
}
_, err = conn.Exec(ctx, "reset role")
return err
}

View File

@ -28,9 +28,8 @@ func NewRouter(db *Db) http.Handler {
companyRouter.POST("/invoices", HandleAddInvoice)
companyRouter.GET("/invoices/:slug", ServeInvoice)
companyRouter.PUT("/invoices/:slug", HandleUpdateInvoice)
companyRouter.POST("/invoices/:slug", HandleNewInvoiceAction)
companyRouter.GET("/invoices/:slug/edit", ServeEditInvoice)
companyRouter.POST("/invoices/:slug/edit", HandleEditInvoiceAction)
companyRouter.POST("/invoices/new", HandleNewInvoiceAction)
companyRouter.POST("/invoices/batch", HandleBatchInvoiceAction)
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
})

152
po/ca.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-03-13 14:50+0100\n"
"POT-Creation-Date: 2023-03-10 13:59+0100\n"
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -18,82 +18,79 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
#: web/template/invoices/products.gohtml:2
#: web/template/invoices/products.gohtml:19
#: web/template/invoices/products.gohtml:15
msgctxt "title"
msgid "Add Products to Invoice"
msgstr "Afegeix productes a la factura"
#: web/template/invoices/products.gohtml:9 web/template/invoices/new.gohtml:9
#: web/template/invoices/index.gohtml:8 web/template/invoices/view.gohtml:9
#: web/template/invoices/edit.gohtml:9 web/template/contacts/new.gohtml:9
#: web/template/contacts/index.gohtml:8 web/template/contacts/edit.gohtml:9
#: web/template/profile.gohtml:9 web/template/tax-details.gohtml:8
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:8
#: web/template/products/edit.gohtml:9
#: web/template/contacts/new.gohtml:9 web/template/contacts/index.gohtml:8
#: web/template/contacts/edit.gohtml:9 web/template/profile.gohtml:9
#: web/template/tax-details.gohtml:8 web/template/products/new.gohtml:9
#: web/template/products/index.gohtml:8 web/template/products/edit.gohtml:9
msgctxt "title"
msgid "Home"
msgstr "Inici"
#: web/template/invoices/products.gohtml:10 web/template/invoices/new.gohtml:10
#: web/template/invoices/index.gohtml:2 web/template/invoices/index.gohtml:9
#: web/template/invoices/view.gohtml:10 web/template/invoices/edit.gohtml:10
#: web/template/invoices/view.gohtml:10
msgctxt "title"
msgid "Invoices"
msgstr "Factures"
#: web/template/invoices/products.gohtml:12 web/template/invoices/new.gohtml:2
#: web/template/invoices/products.gohtml:11 web/template/invoices/new.gohtml:2
#: web/template/invoices/new.gohtml:11 web/template/invoices/new.gohtml:15
msgctxt "title"
msgid "New Invoice"
msgstr "Nova factura"
#: web/template/invoices/products.gohtml:47
#: web/template/invoices/products.gohtml:42
#: web/template/products/index.gohtml:21
msgctxt "product"
msgid "All"
msgstr "Tots"
#: web/template/invoices/products.gohtml:48
#: web/template/invoices/products.gohtml:43
#: web/template/products/index.gohtml:22
msgctxt "title"
msgid "Name"
msgstr "Nom"
#: web/template/invoices/products.gohtml:49
#: web/template/invoices/view.gohtml:56 web/template/products/index.gohtml:23
#: web/template/invoices/products.gohtml:44
#: web/template/invoices/view.gohtml:54 web/template/products/index.gohtml:23
msgctxt "title"
msgid "Price"
msgstr "Preu"
#: web/template/invoices/products.gohtml:63
#: web/template/invoices/products.gohtml:58
#: web/template/products/index.gohtml:37
msgid "No products added yet."
msgstr "No hi ha cap producte."
#: web/template/invoices/products.gohtml:71 web/template/invoices/new.gohtml:63
#: web/template/invoices/edit.gohtml:64
#: web/template/invoices/products.gohtml:66 web/template/invoices/new.gohtml:62
msgctxt "action"
msgid "Add products"
msgstr "Afegeix productes"
#: web/template/invoices/new.gohtml:44 web/template/invoices/view.gohtml:61
#: web/template/invoices/edit.gohtml:45
#: web/template/invoices/new.gohtml:43 web/template/invoices/view.gohtml:59
msgctxt "title"
msgid "Subtotal"
msgstr "Subtotal"
#: web/template/invoices/new.gohtml:54 web/template/invoices/view.gohtml:65
#: web/template/invoices/view.gohtml:105 web/template/invoices/edit.gohtml:55
#: web/template/invoices/new.gohtml:53 web/template/invoices/view.gohtml:63
#: web/template/invoices/view.gohtml:103
msgctxt "title"
msgid "Total"
msgstr "Total"
#: web/template/invoices/new.gohtml:66 web/template/invoices/edit.gohtml:67
#: web/template/invoices/new.gohtml:65
msgctxt "action"
msgid "Update"
msgstr "Actualitza"
#: web/template/invoices/new.gohtml:68 web/template/invoices/index.gohtml:19
#: web/template/invoices/new.gohtml:67 web/template/invoices/index.gohtml:19
msgctxt "action"
msgid "New invoice"
msgstr "Nova factura"
@ -108,7 +105,7 @@ msgctxt "invoice"
msgid "All"
msgstr "Totes"
#: web/template/invoices/index.gohtml:29 web/template/invoices/view.gohtml:28
#: web/template/invoices/index.gohtml:29 web/template/invoices/view.gohtml:26
msgctxt "title"
msgid "Date"
msgstr "Data"
@ -153,60 +150,45 @@ msgctxt "action"
msgid "Select invoice %v"
msgstr "Selecciona factura %v"
#: web/template/invoices/index.gohtml:92 web/template/invoices/view.gohtml:16
msgctxt "action"
msgid "Edit"
msgstr "Edita"
#: web/template/invoices/index.gohtml:98 web/template/invoices/view.gohtml:15
#: web/template/invoices/index.gohtml:91 web/template/invoices/view.gohtml:14
msgctxt "action"
msgid "Duplicate"
msgstr "Duplica"
#: web/template/invoices/index.gohtml:108
#: web/template/invoices/index.gohtml:101
msgid "No invoices added yet."
msgstr "No hi ha cap factura."
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:27
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:25
msgctxt "title"
msgid "Invoice %s"
msgstr "Factura %s"
#: web/template/invoices/view.gohtml:19
#: web/template/invoices/view.gohtml:17
msgctxt "action"
msgid "Download invoice"
msgstr "Descarrega factura"
#: web/template/invoices/view.gohtml:55
#: web/template/invoices/view.gohtml:53
msgctxt "title"
msgid "Concept"
msgstr "Concepte"
#: web/template/invoices/view.gohtml:58
#: web/template/invoices/view.gohtml:56
msgctxt "title"
msgid "Discount"
msgstr "Descompte"
#: web/template/invoices/view.gohtml:60
#: web/template/invoices/view.gohtml:58
msgctxt "title"
msgid "Units"
msgstr "Unitats"
#: web/template/invoices/view.gohtml:95
#: web/template/invoices/view.gohtml:93
msgctxt "title"
msgid "Tax Base"
msgstr "Base imposable"
#: web/template/invoices/edit.gohtml:2 web/template/invoices/edit.gohtml:15
msgctxt "title"
msgid "Edit Invoice “%s”"
msgstr "Edició de la factura «%s»"
#: web/template/invoices/edit.gohtml:69
msgctxt "action"
msgid "Edit invoice"
msgstr "Edita factura"
#: web/template/dashboard.gohtml:2
msgctxt "title"
msgid "Dashboard"
@ -446,43 +428,44 @@ msgstr "No podeu deixar la contrasenya en blanc."
msgid "Invalid user or password."
msgstr "Nom dusuari o contrasenya incorrectes."
#: pkg/products.go:165 pkg/invoices.go:643
#: pkg/products.go:165 pkg/invoices.go:635
msgctxt "input"
msgid "Name"
msgstr "Nom"
#: pkg/products.go:171 pkg/invoices.go:648
#: pkg/products.go:171 pkg/invoices.go:640
msgctxt "input"
msgid "Description"
msgstr "Descripció"
#: pkg/products.go:176 pkg/invoices.go:652
#: pkg/products.go:176 pkg/invoices.go:644
msgctxt "input"
msgid "Price"
msgstr "Preu"
#: pkg/products.go:186 pkg/invoices.go:678
#: pkg/products.go:186 pkg/invoices.go:670
msgctxt "input"
msgid "Taxes"
msgstr "Imposts"
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:717
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:532
#: pkg/invoices.go:706
msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc."
#: pkg/products.go:207 pkg/invoices.go:718
#: pkg/products.go:207 pkg/invoices.go:707
msgid "Price can not be empty."
msgstr "No podeu deixar el preu en blanc."
#: pkg/products.go:208 pkg/invoices.go:719
#: pkg/products.go:208 pkg/invoices.go:708
msgid "Price must be a number greater than zero."
msgstr "El preu ha de ser un número major a zero."
#: pkg/products.go:210 pkg/invoices.go:727
#: pkg/products.go:210 pkg/invoices.go:716
msgid "Selected tax is not valid."
msgstr "Heu seleccionat un impost que no és vàlid."
#: pkg/products.go:211 pkg/invoices.go:728
#: pkg/products.go:211 pkg/invoices.go:717
msgid "You can only select a tax of each class."
msgstr "Només podeu seleccionar un impost de cada classe."
@ -590,105 +573,88 @@ msgstr "La confirmació no és igual a la contrasenya."
msgid "Selected language is not valid."
msgstr "Heu seleccionat un idioma que no és vàlid."
#: pkg/invoices.go:303
#: pkg/invoices.go:302
msgid "Select a customer to bill."
msgstr "Escolliu un client a facturar."
#: pkg/invoices.go:402
msgid "invoices.zip"
msgstr "factures.zip"
#: pkg/invoices.go:408 pkg/invoices.go:836
#: pkg/invoices.go:397 pkg/invoices.go:426
msgid "Invalid action"
msgstr "Acció invàlida."
#: pkg/invoices.go:452
msgctxt "input"
msgid "Invoice Status"
msgstr "Estat de la factura"
#: pkg/invoices.go:420
msgid "invoices.zip"
msgstr "factures.zip"
#: pkg/invoices.go:458
#: pkg/invoices.go:468
msgctxt "input"
msgid "Customer"
msgstr "Client"
#: pkg/invoices.go:464
#: pkg/invoices.go:474
msgctxt "input"
msgid "Number"
msgstr "Número"
#: pkg/invoices.go:469
#: pkg/invoices.go:479
msgctxt "input"
msgid "Invoice Date"
msgstr "Data de factura"
#: pkg/invoices.go:475
#: pkg/invoices.go:485
msgctxt "input"
msgid "Notes"
msgstr "Notes"
#: pkg/invoices.go:480
#: pkg/invoices.go:490
msgctxt "input"
msgid "Tags"
msgstr "Etiquetes"
#: pkg/invoices.go:486
#: pkg/invoices.go:496
msgctxt "input"
msgid "Payment Method"
msgstr "Mètode de pagament"
#: pkg/invoices.go:523
msgid "Selected invoice status is not valid."
msgstr "Heu seleccionat un estat de factura que no és vàlid."
#: pkg/invoices.go:524
msgid "Selected customer is not valid."
msgstr "Heu seleccionat un client que no és vàlid."
#: pkg/invoices.go:525
#: pkg/invoices.go:533
msgid "Invoice date can not be empty."
msgstr "No podeu deixar la data de la factura en blanc."
#: pkg/invoices.go:526
#: pkg/invoices.go:534
msgid "Invoice date must be a valid date."
msgstr "La data de facturació ha de ser vàlida."
#: pkg/invoices.go:528
#: pkg/invoices.go:536
msgid "Selected payment method is not valid."
msgstr "Heu seleccionat un mètode de pagament que no és vàlid."
#: pkg/invoices.go:633 pkg/invoices.go:638
#: pkg/invoices.go:630
msgctxt "input"
msgid "Id"
msgstr "Identificador"
#: pkg/invoices.go:661
#: pkg/invoices.go:653
msgctxt "input"
msgid "Quantity"
msgstr "Quantitat"
#: pkg/invoices.go:669
#: pkg/invoices.go:661
msgctxt "input"
msgid "Discount (%)"
msgstr "Descompte (%)"
#: pkg/invoices.go:716
msgid "Product ID can not be empty."
msgstr "No podeu deixar lidentificador del producte en blanc."
#: pkg/invoices.go:721
#: pkg/invoices.go:710
msgid "Quantity can not be empty."
msgstr "No podeu deixar la quantitat en blanc."
#: pkg/invoices.go:722
#: pkg/invoices.go:711
msgid "Quantity must be a number greater than zero."
msgstr "La quantitat ha de ser un número major a zero."
#: pkg/invoices.go:724
#: pkg/invoices.go:713
msgid "Discount can not be empty."
msgstr "No podeu deixar el descompte en blanc."
#: pkg/invoices.go:725
#: pkg/invoices.go:714
msgid "Discount must be a percentage between 0 and 100."
msgstr "El descompte ha de ser un percentatge entre 0 i 100."

152
po/es.po
View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-03-13 14:50+0100\n"
"POT-Creation-Date: 2023-03-10 13:59+0100\n"
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -18,82 +18,79 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: web/template/invoices/products.gohtml:2
#: web/template/invoices/products.gohtml:19
#: web/template/invoices/products.gohtml:15
msgctxt "title"
msgid "Add Products to Invoice"
msgstr "Añadir productos a la factura"
#: web/template/invoices/products.gohtml:9 web/template/invoices/new.gohtml:9
#: web/template/invoices/index.gohtml:8 web/template/invoices/view.gohtml:9
#: web/template/invoices/edit.gohtml:9 web/template/contacts/new.gohtml:9
#: web/template/contacts/index.gohtml:8 web/template/contacts/edit.gohtml:9
#: web/template/profile.gohtml:9 web/template/tax-details.gohtml:8
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:8
#: web/template/products/edit.gohtml:9
#: web/template/contacts/new.gohtml:9 web/template/contacts/index.gohtml:8
#: web/template/contacts/edit.gohtml:9 web/template/profile.gohtml:9
#: web/template/tax-details.gohtml:8 web/template/products/new.gohtml:9
#: web/template/products/index.gohtml:8 web/template/products/edit.gohtml:9
msgctxt "title"
msgid "Home"
msgstr "Inicio"
#: web/template/invoices/products.gohtml:10 web/template/invoices/new.gohtml:10
#: web/template/invoices/index.gohtml:2 web/template/invoices/index.gohtml:9
#: web/template/invoices/view.gohtml:10 web/template/invoices/edit.gohtml:10
#: web/template/invoices/view.gohtml:10
msgctxt "title"
msgid "Invoices"
msgstr "Facturas"
#: web/template/invoices/products.gohtml:12 web/template/invoices/new.gohtml:2
#: web/template/invoices/products.gohtml:11 web/template/invoices/new.gohtml:2
#: web/template/invoices/new.gohtml:11 web/template/invoices/new.gohtml:15
msgctxt "title"
msgid "New Invoice"
msgstr "Nueva factura"
#: web/template/invoices/products.gohtml:47
#: web/template/invoices/products.gohtml:42
#: web/template/products/index.gohtml:21
msgctxt "product"
msgid "All"
msgstr "Todos"
#: web/template/invoices/products.gohtml:48
#: web/template/invoices/products.gohtml:43
#: web/template/products/index.gohtml:22
msgctxt "title"
msgid "Name"
msgstr "Nombre"
#: web/template/invoices/products.gohtml:49
#: web/template/invoices/view.gohtml:56 web/template/products/index.gohtml:23
#: web/template/invoices/products.gohtml:44
#: web/template/invoices/view.gohtml:54 web/template/products/index.gohtml:23
msgctxt "title"
msgid "Price"
msgstr "Precio"
#: web/template/invoices/products.gohtml:63
#: web/template/invoices/products.gohtml:58
#: web/template/products/index.gohtml:37
msgid "No products added yet."
msgstr "No hay productos."
#: web/template/invoices/products.gohtml:71 web/template/invoices/new.gohtml:63
#: web/template/invoices/edit.gohtml:64
#: web/template/invoices/products.gohtml:66 web/template/invoices/new.gohtml:62
msgctxt "action"
msgid "Add products"
msgstr "Añadir productos"
#: web/template/invoices/new.gohtml:44 web/template/invoices/view.gohtml:61
#: web/template/invoices/edit.gohtml:45
#: web/template/invoices/new.gohtml:43 web/template/invoices/view.gohtml:59
msgctxt "title"
msgid "Subtotal"
msgstr "Subtotal"
#: web/template/invoices/new.gohtml:54 web/template/invoices/view.gohtml:65
#: web/template/invoices/view.gohtml:105 web/template/invoices/edit.gohtml:55
#: web/template/invoices/new.gohtml:53 web/template/invoices/view.gohtml:63
#: web/template/invoices/view.gohtml:103
msgctxt "title"
msgid "Total"
msgstr "Total"
#: web/template/invoices/new.gohtml:66 web/template/invoices/edit.gohtml:67
#: web/template/invoices/new.gohtml:65
msgctxt "action"
msgid "Update"
msgstr "Actualizar"
#: web/template/invoices/new.gohtml:68 web/template/invoices/index.gohtml:19
#: web/template/invoices/new.gohtml:67 web/template/invoices/index.gohtml:19
msgctxt "action"
msgid "New invoice"
msgstr "Nueva factura"
@ -108,7 +105,7 @@ msgctxt "invoice"
msgid "All"
msgstr "Todas"
#: web/template/invoices/index.gohtml:29 web/template/invoices/view.gohtml:28
#: web/template/invoices/index.gohtml:29 web/template/invoices/view.gohtml:26
msgctxt "title"
msgid "Date"
msgstr "Fecha"
@ -153,60 +150,45 @@ msgctxt "action"
msgid "Select invoice %v"
msgstr "Seleccionar factura %v"
#: web/template/invoices/index.gohtml:92 web/template/invoices/view.gohtml:16
msgctxt "action"
msgid "Edit"
msgstr "Editar"
#: web/template/invoices/index.gohtml:98 web/template/invoices/view.gohtml:15
#: web/template/invoices/index.gohtml:91 web/template/invoices/view.gohtml:14
msgctxt "action"
msgid "Duplicate"
msgstr "Duplicar"
#: web/template/invoices/index.gohtml:108
#: web/template/invoices/index.gohtml:101
msgid "No invoices added yet."
msgstr "No hay facturas."
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:27
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:25
msgctxt "title"
msgid "Invoice %s"
msgstr "Factura %s"
#: web/template/invoices/view.gohtml:19
#: web/template/invoices/view.gohtml:17
msgctxt "action"
msgid "Download invoice"
msgstr "Descargar factura"
#: web/template/invoices/view.gohtml:55
#: web/template/invoices/view.gohtml:53
msgctxt "title"
msgid "Concept"
msgstr "Concepto"
#: web/template/invoices/view.gohtml:58
#: web/template/invoices/view.gohtml:56
msgctxt "title"
msgid "Discount"
msgstr "Descuento"
#: web/template/invoices/view.gohtml:60
#: web/template/invoices/view.gohtml:58
msgctxt "title"
msgid "Units"
msgstr "Unidades"
#: web/template/invoices/view.gohtml:95
#: web/template/invoices/view.gohtml:93
msgctxt "title"
msgid "Tax Base"
msgstr "Base imponible"
#: web/template/invoices/edit.gohtml:2 web/template/invoices/edit.gohtml:15
msgctxt "title"
msgid "Edit Invoice “%s”"
msgstr "Edición del la factura «%s»"
#: web/template/invoices/edit.gohtml:69
msgctxt "action"
msgid "Edit invoice"
msgstr "Editar factura"
#: web/template/dashboard.gohtml:2
msgctxt "title"
msgid "Dashboard"
@ -446,43 +428,44 @@ msgstr "No podéis dejar la contraseña en blanco."
msgid "Invalid user or password."
msgstr "Nombre de usuario o contraseña inválido."
#: pkg/products.go:165 pkg/invoices.go:643
#: pkg/products.go:165 pkg/invoices.go:635
msgctxt "input"
msgid "Name"
msgstr "Nombre"
#: pkg/products.go:171 pkg/invoices.go:648
#: pkg/products.go:171 pkg/invoices.go:640
msgctxt "input"
msgid "Description"
msgstr "Descripción"
#: pkg/products.go:176 pkg/invoices.go:652
#: pkg/products.go:176 pkg/invoices.go:644
msgctxt "input"
msgid "Price"
msgstr "Precio"
#: pkg/products.go:186 pkg/invoices.go:678
#: pkg/products.go:186 pkg/invoices.go:670
msgctxt "input"
msgid "Taxes"
msgstr "Impuestos"
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:717
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:532
#: pkg/invoices.go:706
msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco."
#: pkg/products.go:207 pkg/invoices.go:718
#: pkg/products.go:207 pkg/invoices.go:707
msgid "Price can not be empty."
msgstr "No podéis dejar el precio en blanco."
#: pkg/products.go:208 pkg/invoices.go:719
#: pkg/products.go:208 pkg/invoices.go:708
msgid "Price must be a number greater than zero."
msgstr "El precio tiene que ser un número mayor a cero."
#: pkg/products.go:210 pkg/invoices.go:727
#: pkg/products.go:210 pkg/invoices.go:716
msgid "Selected tax is not valid."
msgstr "Habéis escogido un impuesto que no es válido."
#: pkg/products.go:211 pkg/invoices.go:728
#: pkg/products.go:211 pkg/invoices.go:717
msgid "You can only select a tax of each class."
msgstr "Solo podéis escoger un impuesto de cada clase."
@ -590,105 +573,88 @@ msgstr "La confirmación no corresponde con la contraseña."
msgid "Selected language is not valid."
msgstr "Habéis escogido un idioma que no es válido."
#: pkg/invoices.go:303
#: pkg/invoices.go:302
msgid "Select a customer to bill."
msgstr "Escoged un cliente a facturar."
#: pkg/invoices.go:402
msgid "invoices.zip"
msgstr "facturas.zip"
#: pkg/invoices.go:408 pkg/invoices.go:836
#: pkg/invoices.go:397 pkg/invoices.go:426
msgid "Invalid action"
msgstr "Acción inválida."
#: pkg/invoices.go:452
msgctxt "input"
msgid "Invoice Status"
msgstr "Estado de la factura"
#: pkg/invoices.go:420
msgid "invoices.zip"
msgstr "facturas.zip"
#: pkg/invoices.go:458
#: pkg/invoices.go:468
msgctxt "input"
msgid "Customer"
msgstr "Cliente"
#: pkg/invoices.go:464
#: pkg/invoices.go:474
msgctxt "input"
msgid "Number"
msgstr "Número"
#: pkg/invoices.go:469
#: pkg/invoices.go:479
msgctxt "input"
msgid "Invoice Date"
msgstr "Fecha de factura"
#: pkg/invoices.go:475
#: pkg/invoices.go:485
msgctxt "input"
msgid "Notes"
msgstr "Notas"
#: pkg/invoices.go:480
#: pkg/invoices.go:490
msgctxt "input"
msgid "Tags"
msgstr "Etiquetes"
#: pkg/invoices.go:486
#: pkg/invoices.go:496
msgctxt "input"
msgid "Payment Method"
msgstr "Método de pago"
#: pkg/invoices.go:523
msgid "Selected invoice status is not valid."
msgstr "Habéis escogido un estado de factura que no es válido."
#: pkg/invoices.go:524
msgid "Selected customer is not valid."
msgstr "Habéis escogido un cliente que no es válido."
#: pkg/invoices.go:525
#: pkg/invoices.go:533
msgid "Invoice date can not be empty."
msgstr "No podéis dejar la fecha de la factura en blanco."
#: pkg/invoices.go:526
#: pkg/invoices.go:534
msgid "Invoice date must be a valid date."
msgstr "La fecha de factura debe ser válida."
#: pkg/invoices.go:528
#: pkg/invoices.go:536
msgid "Selected payment method is not valid."
msgstr "Habéis escogido un método de pago que no es válido."
#: pkg/invoices.go:633 pkg/invoices.go:638
#: pkg/invoices.go:630
msgctxt "input"
msgid "Id"
msgstr "Identificador"
#: pkg/invoices.go:661
#: pkg/invoices.go:653
msgctxt "input"
msgid "Quantity"
msgstr "Cantidad"
#: pkg/invoices.go:669
#: pkg/invoices.go:661
msgctxt "input"
msgid "Discount (%)"
msgstr "Descuento (%)"
#: pkg/invoices.go:716
msgid "Product ID can not be empty."
msgstr "No podéis dejar el identificador de producto en blanco."
#: pkg/invoices.go:721
#: pkg/invoices.go:710
msgid "Quantity can not be empty."
msgstr "No podéis dejar la cantidad en blanco."
#: pkg/invoices.go:722
#: pkg/invoices.go:711
msgid "Quantity must be a number greater than zero."
msgstr "La cantidad tiene que ser un número mayor a cero."
#: pkg/invoices.go:724
#: pkg/invoices.go:713
msgid "Discount can not be empty."
msgstr "No podéis dejar el descuento en blanco."
#: pkg/invoices.go:725
#: pkg/invoices.go:714
msgid "Discount must be a percentage between 0 and 100."
msgstr "El descuento tiene que ser un porcentaje entre 0 y 100."

View File

@ -93,8 +93,7 @@ values (19, 15, 7, 'P1.0', 1100)
;
insert into invoice_product_tax (invoice_product_id, tax_id, tax_rate)
values (19, 4, 0.21)
, (20, 4, 0.21)
values (20, 4, 0.21)
, (21, 3, -0.07)
, (21, 4, 0.21)
, (22, 3, -0.15)

View File

@ -1,74 +0,0 @@
{{ define "title" -}}
{{ printf ( pgettext "Edit Invoice “%s”" "title" ) .Number }}
{{- end }}
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.editInvoicePage*/ -}}
<nav>
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a href="{{ companyURI "/invoices"}}">{{( pgettext "Invoices" "title" )}}</a> /
<a>{{ .Number }}</a>
</p>
</nav>
<section class="dialog-content">
<h2>{{ printf (pgettext "Edit Invoice “%s”" "title") .Number }}</h2>
<form method="POST" action="{{ companyURI "/invoices/" }}{{ .Slug }}">
{{ csrfToken }}
{{ with .Form -}}
{{ template "select-field" .Customer }}
{{ template "hidden-field" .Number }}
{{ template "hidden-field" .Date }}
{{ template "input-field" .Tags }}
{{ template "select-field" .PaymentMethod }}
{{ template "select-field" .InvoiceStatus }}
{{ template "input-field" .Notes }}
{{- range $product := .Products }}
<fieldset class="new-invoice-product">
{{ template "hidden-field" .InvoiceProductId }}
{{ template "hidden-field" .ProductId }}
{{ template "input-field" .Name }}
{{ template "input-field" .Price }}
{{ template "input-field" .Quantity }}
{{ template "input-field" .Discount }}
{{ template "input-field" .Description }}
{{ template "select-field" .Tax }}
</fieldset>
{{- end }}
{{- end }}
<table>
<tbody>
<tr>
<th scope="row">{{(pgettext "Subtotal" "title")}}</th>
<td class="numeric">{{ .Subtotal | formatPrice }}</td>
</tr>
{{- range $tax := .Taxes }}
<tr>
<th scope="row">{{ index . 0 }}</th>
<td class="numeric">{{ index . 1 | formatPrice }}</td>
</tr>
{{- end }}
<tr>
<th scope="row">{{(pgettext "Total" "title")}}</th>
<td class="numeric">{{ .Total | formatPrice }}</td>
</tr>
</tbody>
</table>
<fieldset>
<button formnovalidate formaction="{{ companyURI "/invoices" }}/{{ .Slug }}/edit"
name="action" value="select-products"
type="submit">{{( pgettext "Add products" "action" )}}</button>
<button formnovalidate formaction="{{ companyURI "/invoices" }}/{{ .Slug }}/edit"
name="action" value="update"
type="submit">{{( pgettext "Update" "action" )}}</button>
<button class="primary" name="_method" value="PUT"
type="submit">{{( pgettext "Edit invoice" "action" )}}</button>
</fieldset>
</form>
</section>
{{- end }}

View File

@ -54,13 +54,12 @@
<form action="{{companyURI "/invoices/"}}{{ .Slug }}" method="POST">
{{ csrfToken }}
{{ putMethod }}
<input type="hidden" name="quick" value="status">
<ul role="menu">
{{- range $status, $name := $.InvoiceStatuses }}
{{- if ne $status $invoice.Status }}
<li role="presentation">
<button role="menuitem" type="submit"
name="invoice_status" value="{{ $status }}"
name="status" value="{{ $status }}"
class="invoice-status-{{ $status }}"
>{{ $name }}</button>
</li>
@ -86,12 +85,6 @@
<details class="menu">
<summary><i class="ri-more-line"></i></summary>
<ul role="menu" class="action-menu">
<li role="presentation">
<a role="menuitem" href="{{ companyURI "/invoices"}}/{{ .Slug }}/edit">
<i class="ri-edit-line"></i>
{{( pgettext "Edit" "action" )}}
</a>
</li>
<li role="presentation">
<a role="menuitem" href="{{ companyURI "/invoices/new"}}?duplicate={{ .Slug }}">
<i class="ri-file-copy-line"></i>

View File

@ -3,7 +3,7 @@
{{- end }}
{{ define "content" }}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.newInvoicePage*/ -}}
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.invoiceForm*/ -}}
<nav>
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
@ -17,7 +17,6 @@
{{ csrfToken }}
{{ with .Form -}}
{{ template "hidden-select-field" .InvoiceStatus }}
{{ template "select-field" .Customer }}
{{ template "input-field" .Number }}
{{ template "input-field" .Date }}

View File

@ -8,16 +8,12 @@
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a href="{{ companyURI "/invoices"}}">{{( pgettext "Invoices" "title" )}}</a> /
{{ if eq .Form.Number.Val "" }}
<a>{{( pgettext "New Invoice" "title" )}}</a>
{{ else }}
<a>{{ .Form.Number }}</a>
{{ end }}
</p>
</nav>
<section class="dialog-content">
<h2>{{(pgettext "Add Products to Invoice" "title")}}</h2>
<form method="POST" action="{{ .Action }}">
<form method="POST" action="{{ companyURI "/invoices/new" }}">
{{ csrfToken }}
{{- with .Form }}
@ -29,7 +25,6 @@
{{- range $product := .Products }}
<fieldset>
{{ template "hidden-field" .InvoiceProductId }}
{{ template "hidden-field" .ProductId }}
{{ template "hidden-field" .Name }}
{{ template "hidden-field" .Description }}

View File

@ -11,9 +11,7 @@
<a>{{ .Number }}</a>
</p>
<p>
<a class="button primary"
href="{{ companyURI "/invoices/new"}}?duplicate={{ .Slug }}">{{( pgettext "Duplicate" "action" )}}</a>
<a class="button primary" href="{{ companyURI "/invoices/"}}{{ .Slug }}/edit">{{( pgettext "Edit" "action" )}}</a>
<a class="primary button" href="{{ companyURI "/invoices/new"}}?duplicate={{ .Slug }}">{{( pgettext "Duplicate" "action" )}}</a>
<a class="primary button"
href="{{ companyURI "/invoices/" }}{{ .Slug }}.pdf"
download="{{ .Number}}.pdf">{{( pgettext "Download invoice" "action" )}}</a>

View File

@ -1,5 +1,5 @@
{{ define "title" -}}
{{printf (pgettext "Edit Product “%s”" "title") .Name }}
{{printf (pgettext "Edit Product “%s”" "title") .Name.Val }}
{{- end }}
{{ define "content" }}
@ -8,11 +8,11 @@
<p>
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
<a href="{{ companyURI "/products"}}">{{( pgettext "Products" "title" )}}</a> /
<a>{{ .Name }}</a>
<a>{{ .Name.Val }}</a>
</p>
</nav>
<section class="dialog-content">
<h2>{{printf (pgettext "Edit Product “%s”" "title") .Name }}</h2>
<h2>{{printf (pgettext "Edit Product “%s”" "title") .Name.Val }}</h2>
<form method="POST">
{{ csrfToken }}
{{ putMethod }}