Add the edit form for invoices
I had to change the way /invoices/new and /invoices/batch are handled, because httprouter was not happy with the new POST /invoices/:slug/edit route, claiming that /invoices/:slug conflicts with the previously existing routes. I also could not make it work with the PATCH method, even though i correctly added the patchMethod override function, therefore editing invoices is also weird because i have to take into account the “quick” invoice status change. I use the same form for both new and edit invoices, because the only changes are that we can not edit the invoice date and number, by Oriol’s design, but must be able to change the status; very similar forms.
This commit is contained in:
parent
1ab48d2947
commit
8efae0485e
185
pkg/invoices.go
185
pkg/invoices.go
|
@ -117,14 +117,15 @@ func mustCollectInvoiceStatuses(ctx context.Context, conn *Conn, locale *Locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func ServeInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
locale := getLocale(r)
|
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
company := mustGetCompany(r)
|
company := mustGetCompany(r)
|
||||||
form := newInvoiceForm(r.Context(), conn, locale, company)
|
|
||||||
slug := params[0].Value
|
slug := params[0].Value
|
||||||
if slug == "new" {
|
if slug == "new" {
|
||||||
|
locale := getLocale(r)
|
||||||
|
form := newInvoiceForm(r.Context(), conn, locale, company)
|
||||||
if invoiceToDuplicate := r.URL.Query().Get("duplicate"); invoiceToDuplicate != "" {
|
if invoiceToDuplicate := r.URL.Query().Get("duplicate"); invoiceToDuplicate != "" {
|
||||||
form.MustFillFromDatabase(r.Context(), conn, invoiceToDuplicate)
|
form.MustFillFromDatabase(r.Context(), conn, invoiceToDuplicate)
|
||||||
|
form.InvoiceStatus.Selected = []string{"created"}
|
||||||
form.Number.Val = ""
|
form.Number.Val = ""
|
||||||
}
|
}
|
||||||
form.Date.Val = time.Now().Format("2006-01-02")
|
form.Date.Val = time.Now().Format("2006-01-02")
|
||||||
|
@ -304,10 +305,11 @@ func mustRenderNewInvoiceForm(w http.ResponseWriter, r *http.Request, form *invo
|
||||||
mustRenderAppTemplate(w, r, "invoices/new.gohtml", page)
|
mustRenderAppTemplate(w, r, "invoices/new.gohtml", page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustRenderNewInvoiceProductsForm(w http.ResponseWriter, r *http.Request, form *invoiceForm) {
|
func mustRenderNewInvoiceProductsForm(w http.ResponseWriter, r *http.Request, action string, form *invoiceForm) {
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
company := mustGetCompany(r)
|
company := mustGetCompany(r)
|
||||||
page := newInvoiceProductsPage{
|
page := newInvoiceProductsPage{
|
||||||
|
Action: companyURI(company, action),
|
||||||
Form: form,
|
Form: form,
|
||||||
Products: mustGetProductChoices(r.Context(), conn, company),
|
Products: mustGetProductChoices(r.Context(), conn, company),
|
||||||
}
|
}
|
||||||
|
@ -334,6 +336,7 @@ func mustGetProductChoices(ctx context.Context, conn *Conn, company *Company) []
|
||||||
}
|
}
|
||||||
|
|
||||||
type newInvoiceProductsPage struct {
|
type newInvoiceProductsPage struct {
|
||||||
|
Action string
|
||||||
Form *invoiceForm
|
Form *invoiceForm
|
||||||
Products []*productChoice
|
Products []*productChoice
|
||||||
}
|
}
|
||||||
|
@ -362,39 +365,18 @@ func HandleAddInvoice(w http.ResponseWriter, r *http.Request, _ httprouter.Param
|
||||||
mustRenderNewInvoiceForm(w, r, form)
|
mustRenderNewInvoiceForm(w, r, form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
reg := regexp.MustCompile("[^a-z0-9-]+")
|
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))
|
||||||
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)
|
http.Redirect(w, r, companyURI(company, "/invoices/"+slug), http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleNewInvoiceAction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
func HandleNewInvoiceAction(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
locale := getLocale(r)
|
switch params[0].Value {
|
||||||
conn := getConn(r)
|
case "new":
|
||||||
company := mustGetCompany(r)
|
handleInvoiceAction(w, r, "/invoices/new", mustRenderNewInvoiceForm)
|
||||||
form := newInvoiceForm(r.Context(), conn, locale, company)
|
case "batch":
|
||||||
if err := form.Parse(r); err != nil {
|
HandleBatchInvoiceAction(w, r, params)
|
||||||
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:
|
default:
|
||||||
http.Error(w, gettext("Invalid action", locale), http.StatusBadRequest)
|
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,6 +432,7 @@ func mustWriteInvoicesPdf(r *http.Request, slugs []string) []byte {
|
||||||
type invoiceForm struct {
|
type invoiceForm struct {
|
||||||
locale *Locale
|
locale *Locale
|
||||||
company *Company
|
company *Company
|
||||||
|
InvoiceStatus *SelectField
|
||||||
Customer *SelectField
|
Customer *SelectField
|
||||||
Number *InputField
|
Number *InputField
|
||||||
Date *InputField
|
Date *InputField
|
||||||
|
@ -463,6 +446,13 @@ func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Co
|
||||||
return &invoiceForm{
|
return &invoiceForm{
|
||||||
locale: locale,
|
locale: locale,
|
||||||
company: company,
|
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{
|
Customer: &SelectField{
|
||||||
Name: "customer",
|
Name: "customer",
|
||||||
Label: pgettext("input", "Customer", locale),
|
Label: pgettext("input", "Customer", locale),
|
||||||
|
@ -504,6 +494,7 @@ func (form *invoiceForm) Parse(r *http.Request) error {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
form.InvoiceStatus.FillValue(r)
|
||||||
form.Customer.FillValue(r)
|
form.Customer.FillValue(r)
|
||||||
form.Number.FillValue(r)
|
form.Number.FillValue(r)
|
||||||
form.Date.FillValue(r)
|
form.Date.FillValue(r)
|
||||||
|
@ -529,7 +520,8 @@ func (form *invoiceForm) Parse(r *http.Request) error {
|
||||||
func (form *invoiceForm) Validate() bool {
|
func (form *invoiceForm) Validate() bool {
|
||||||
validator := newFormValidator()
|
validator := newFormValidator()
|
||||||
|
|
||||||
validator.CheckValidSelectOption(form.Customer, gettext("Name can not be empty.", form.locale))
|
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))
|
||||||
if validator.CheckRequiredInput(form.Date, gettext("Invoice date 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))
|
validator.CheckValidDate(form.Date, gettext("Invoice date must be a valid date.", form.locale))
|
||||||
}
|
}
|
||||||
|
@ -556,7 +548,7 @@ func (form *invoiceForm) Update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (form *invoiceForm) AddProducts(ctx context.Context, conn *Conn, productsId []string) {
|
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{}) {
|
func (form *invoiceForm) mustAddProductsFromQuery(ctx context.Context, conn *Conn, sql string, args ...interface{}) {
|
||||||
|
@ -566,7 +558,7 @@ func (form *invoiceForm) mustAddProductsFromQuery(ctx context.Context, conn *Con
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
product := newInvoiceProductForm(index, form.company, form.locale, taxOptions)
|
product := newInvoiceProductForm(index, form.company, form.locale, taxOptions)
|
||||||
if err := rows.Scan(product.ProductId, product.Name, product.Description, product.Price, product.Quantity, product.Discount, product.Tax); err != nil {
|
if err := rows.Scan(product.InvoiceProductId, product.ProductId, product.Name, product.Description, product.Price, product.Quantity, product.Discount, product.Tax); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
form.Products = append(form.Products, product)
|
form.Products = append(form.Products, product)
|
||||||
|
@ -577,12 +569,15 @@ func (form *invoiceForm) mustAddProductsFromQuery(ctx context.Context, conn *Con
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (form *invoiceForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) {
|
func (form *invoiceForm) MustFillFromDatabase(ctx context.Context, conn *Conn, slug string) bool {
|
||||||
var invoiceId int
|
var invoiceId int
|
||||||
|
selectedInvoiceStatus := form.InvoiceStatus.Selected
|
||||||
|
form.InvoiceStatus.Clear()
|
||||||
selectedPaymentMethod := form.PaymentMethod.Selected
|
selectedPaymentMethod := form.PaymentMethod.Selected
|
||||||
form.PaymentMethod.Clear()
|
form.PaymentMethod.Clear()
|
||||||
if notFoundErrorOrPanic(conn.QueryRow(ctx, `
|
if notFoundErrorOrPanic(conn.QueryRow(ctx, `
|
||||||
select invoice_id
|
select invoice_id
|
||||||
|
, invoice_status
|
||||||
, contact_id
|
, contact_id
|
||||||
, invoice_number
|
, invoice_number
|
||||||
, invoice_date
|
, invoice_date
|
||||||
|
@ -598,21 +593,29 @@ func (form *invoiceForm) MustFillFromDatabase(ctx context.Context, conn *Conn, s
|
||||||
, invoice_date
|
, invoice_date
|
||||||
, notes
|
, notes
|
||||||
, payment_method_id
|
, payment_method_id
|
||||||
`, slug).Scan(&invoiceId, 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.PaymentMethod.Selected = selectedPaymentMethod
|
||||||
return
|
form.InvoiceStatus.Selected = selectedInvoiceStatus
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
form.Products = []*invoiceProductForm{}
|
form.Products = []*invoiceProductForm{}
|
||||||
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)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetTaxOptions(ctx context.Context, conn *Conn, company *Company) []*SelectOption {
|
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)
|
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 {
|
type invoiceProductForm struct {
|
||||||
locale *Locale
|
locale *Locale
|
||||||
company *Company
|
company *Company
|
||||||
|
InvoiceProductId *InputField
|
||||||
ProductId *InputField
|
ProductId *InputField
|
||||||
Name *InputField
|
Name *InputField
|
||||||
Description *InputField
|
Description *InputField
|
||||||
|
@ -626,6 +629,11 @@ func newInvoiceProductForm(index int, company *Company, locale *Locale, taxOptio
|
||||||
form := &invoiceProductForm{
|
form := &invoiceProductForm{
|
||||||
locale: locale,
|
locale: locale,
|
||||||
company: company,
|
company: company,
|
||||||
|
InvoiceProductId: &InputField{
|
||||||
|
Label: pgettext("input", "Id", locale),
|
||||||
|
Type: "hidden",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
ProductId: &InputField{
|
ProductId: &InputField{
|
||||||
Label: pgettext("input", "Id", locale),
|
Label: pgettext("input", "Id", locale),
|
||||||
Type: "hidden",
|
Type: "hidden",
|
||||||
|
@ -678,6 +686,7 @@ func newInvoiceProductForm(index int, company *Company, locale *Locale, taxOptio
|
||||||
|
|
||||||
func (form *invoiceProductForm) Reindex(index int) {
|
func (form *invoiceProductForm) Reindex(index int) {
|
||||||
suffix := "." + strconv.Itoa(index)
|
suffix := "." + strconv.Itoa(index)
|
||||||
|
form.InvoiceProductId.Name = "product.invoice_product_id" + suffix
|
||||||
form.ProductId.Name = "product.id" + suffix
|
form.ProductId.Name = "product.id" + suffix
|
||||||
form.Name.Name = "product.name" + suffix
|
form.Name.Name = "product.name" + suffix
|
||||||
form.Description.Name = "product.description" + suffix
|
form.Description.Name = "product.description" + suffix
|
||||||
|
@ -691,6 +700,7 @@ func (form *invoiceProductForm) Parse(r *http.Request) error {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
form.InvoiceProductId.FillValue(r)
|
||||||
form.ProductId.FillValue(r)
|
form.ProductId.FillValue(r)
|
||||||
form.Name.FillValue(r)
|
form.Name.FillValue(r)
|
||||||
form.Description.FillValue(r)
|
form.Description.FillValue(r)
|
||||||
|
@ -703,6 +713,7 @@ func (form *invoiceProductForm) Parse(r *http.Request) error {
|
||||||
|
|
||||||
func (form *invoiceProductForm) Validate() bool {
|
func (form *invoiceProductForm) Validate() bool {
|
||||||
validator := newFormValidator()
|
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))
|
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)) {
|
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))
|
validator.CheckValidDecimal(form.Price, form.company.MinCents(), math.MaxFloat64, gettext("Price must be a number greater than zero.", form.locale))
|
||||||
|
@ -719,8 +730,11 @@ func (form *invoiceProductForm) Validate() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleUpdateInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
func HandleUpdateInvoice(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||||
|
locale := getLocale(r)
|
||||||
conn := getConn(r)
|
conn := getConn(r)
|
||||||
if err := r.ParseForm(); err != nil {
|
company := mustGetCompany(r)
|
||||||
|
form := newInvoiceForm(r.Context(), conn, locale, company)
|
||||||
|
if err := form.Parse(r); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -728,10 +742,97 @@ func HandleUpdateInvoice(w http.ResponseWriter, r *http.Request, params httprout
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
invoiceStatus := r.FormValue("status")
|
if r.FormValue("quick") == "status" {
|
||||||
slug := conn.MustGetText(r.Context(), "", "update invoice set invoice_status = $1 where slug = $2 returning slug", invoiceStatus, params[0].Value)
|
slug := conn.MustGetText(r.Context(), "", "update invoice set invoice_status = $1 where slug = $2 returning slug", form.InvoiceStatus, params[0].Value)
|
||||||
if slug == "" {
|
if slug == "" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, companyURI(mustGetCompany(r), "/invoices"), http.StatusSeeOther)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,40 @@ func (src NewInvoiceProductArray) EncodeBinary(ci *pgtype.ConnInfo, buf []byte)
|
||||||
return array.EncodeBinary(ci, buf)
|
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 {
|
func registerPgTypes(ctx context.Context, conn *pgx.Conn) error {
|
||||||
if _, err := conn.Exec(ctx, "set role to admin"); err != nil {
|
if _, err := conn.Exec(ctx, "set role to admin"); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -71,6 +105,36 @@ func registerPgTypes(ctx context.Context, conn *pgx.Conn) error {
|
||||||
return err
|
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")
|
_, err = conn.Exec(ctx, "reset role")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,9 @@ func NewRouter(db *Db) http.Handler {
|
||||||
companyRouter.POST("/invoices", HandleAddInvoice)
|
companyRouter.POST("/invoices", HandleAddInvoice)
|
||||||
companyRouter.GET("/invoices/:slug", ServeInvoice)
|
companyRouter.GET("/invoices/:slug", ServeInvoice)
|
||||||
companyRouter.PUT("/invoices/:slug", HandleUpdateInvoice)
|
companyRouter.PUT("/invoices/:slug", HandleUpdateInvoice)
|
||||||
companyRouter.POST("/invoices/new", HandleNewInvoiceAction)
|
companyRouter.POST("/invoices/:slug", HandleNewInvoiceAction)
|
||||||
companyRouter.POST("/invoices/batch", HandleBatchInvoiceAction)
|
companyRouter.GET("/invoices/:slug/edit", ServeEditInvoice)
|
||||||
|
companyRouter.POST("/invoices/:slug/edit", HandleEditInvoiceAction)
|
||||||
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
|
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
|
||||||
})
|
})
|
||||||
|
|
154
po/ca.po
154
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: numerus\n"
|
"Project-Id-Version: numerus\n"
|
||||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||||
"POT-Creation-Date: 2023-03-10 13:59+0100\n"
|
"POT-Creation-Date: 2023-03-13 14:50+0100\n"
|
||||||
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
"PO-Revision-Date: 2023-01-18 17:08+0100\n"
|
||||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||||
|
@ -18,79 +18,82 @@ msgstr ""
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:2
|
#: web/template/invoices/products.gohtml:2
|
||||||
#: web/template/invoices/products.gohtml:15
|
#: web/template/invoices/products.gohtml:19
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Add Products to Invoice"
|
msgid "Add Products to Invoice"
|
||||||
msgstr "Afegeix productes a la factura"
|
msgstr "Afegeix productes a la factura"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:9 web/template/invoices/new.gohtml:9
|
#: 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/index.gohtml:8 web/template/invoices/view.gohtml:9
|
||||||
#: web/template/contacts/new.gohtml:9 web/template/contacts/index.gohtml:8
|
#: web/template/invoices/edit.gohtml:9 web/template/contacts/new.gohtml:9
|
||||||
#: web/template/contacts/edit.gohtml:9 web/template/profile.gohtml:9
|
#: web/template/contacts/index.gohtml:8 web/template/contacts/edit.gohtml:9
|
||||||
#: web/template/tax-details.gohtml:8 web/template/products/new.gohtml:9
|
#: web/template/profile.gohtml:9 web/template/tax-details.gohtml:8
|
||||||
#: web/template/products/index.gohtml:8 web/template/products/edit.gohtml:9
|
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:8
|
||||||
|
#: web/template/products/edit.gohtml:9
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Inici"
|
msgstr "Inici"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:10 web/template/invoices/new.gohtml:10
|
#: 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/index.gohtml:2 web/template/invoices/index.gohtml:9
|
||||||
#: web/template/invoices/view.gohtml:10
|
#: web/template/invoices/view.gohtml:10 web/template/invoices/edit.gohtml:10
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Invoices"
|
msgid "Invoices"
|
||||||
msgstr "Factures"
|
msgstr "Factures"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:11 web/template/invoices/new.gohtml:2
|
#: web/template/invoices/products.gohtml:12 web/template/invoices/new.gohtml:2
|
||||||
#: web/template/invoices/new.gohtml:11 web/template/invoices/new.gohtml:15
|
#: web/template/invoices/new.gohtml:11 web/template/invoices/new.gohtml:15
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "New Invoice"
|
msgid "New Invoice"
|
||||||
msgstr "Nova factura"
|
msgstr "Nova factura"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:42
|
#: web/template/invoices/products.gohtml:47
|
||||||
#: web/template/products/index.gohtml:21
|
#: web/template/products/index.gohtml:21
|
||||||
msgctxt "product"
|
msgctxt "product"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Tots"
|
msgstr "Tots"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:43
|
#: web/template/invoices/products.gohtml:48
|
||||||
#: web/template/products/index.gohtml:22
|
#: web/template/products/index.gohtml:22
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nom"
|
msgstr "Nom"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:44
|
#: web/template/invoices/products.gohtml:49
|
||||||
#: web/template/invoices/view.gohtml:54 web/template/products/index.gohtml:23
|
#: web/template/invoices/view.gohtml:56 web/template/products/index.gohtml:23
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Preu"
|
msgstr "Preu"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:58
|
#: web/template/invoices/products.gohtml:63
|
||||||
#: web/template/products/index.gohtml:37
|
#: web/template/products/index.gohtml:37
|
||||||
msgid "No products added yet."
|
msgid "No products added yet."
|
||||||
msgstr "No hi ha cap producte."
|
msgstr "No hi ha cap producte."
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:66 web/template/invoices/new.gohtml:62
|
#: web/template/invoices/products.gohtml:71 web/template/invoices/new.gohtml:63
|
||||||
|
#: web/template/invoices/edit.gohtml:64
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Add products"
|
msgid "Add products"
|
||||||
msgstr "Afegeix productes"
|
msgstr "Afegeix productes"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:43 web/template/invoices/view.gohtml:59
|
#: web/template/invoices/new.gohtml:44 web/template/invoices/view.gohtml:61
|
||||||
|
#: web/template/invoices/edit.gohtml:45
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Subtotal"
|
msgid "Subtotal"
|
||||||
msgstr "Subtotal"
|
msgstr "Subtotal"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:53 web/template/invoices/view.gohtml:63
|
#: web/template/invoices/new.gohtml:54 web/template/invoices/view.gohtml:65
|
||||||
#: web/template/invoices/view.gohtml:103
|
#: web/template/invoices/view.gohtml:105 web/template/invoices/edit.gohtml:55
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr "Total"
|
msgstr "Total"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:65
|
#: web/template/invoices/new.gohtml:66 web/template/invoices/edit.gohtml:67
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Update"
|
msgid "Update"
|
||||||
msgstr "Actualitza"
|
msgstr "Actualitza"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:67 web/template/invoices/index.gohtml:19
|
#: web/template/invoices/new.gohtml:68 web/template/invoices/index.gohtml:19
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "New invoice"
|
msgid "New invoice"
|
||||||
msgstr "Nova factura"
|
msgstr "Nova factura"
|
||||||
|
@ -105,7 +108,7 @@ msgctxt "invoice"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Totes"
|
msgstr "Totes"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:29 web/template/invoices/view.gohtml:26
|
#: web/template/invoices/index.gohtml:29 web/template/invoices/view.gohtml:28
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "Data"
|
msgstr "Data"
|
||||||
|
@ -150,45 +153,60 @@ msgctxt "action"
|
||||||
msgid "Select invoice %v"
|
msgid "Select invoice %v"
|
||||||
msgstr "Selecciona factura %v"
|
msgstr "Selecciona factura %v"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:91 web/template/invoices/view.gohtml:14
|
#: 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
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Duplicate"
|
msgid "Duplicate"
|
||||||
msgstr "Duplica"
|
msgstr "Duplica"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:101
|
#: web/template/invoices/index.gohtml:108
|
||||||
msgid "No invoices added yet."
|
msgid "No invoices added yet."
|
||||||
msgstr "No hi ha cap factura."
|
msgstr "No hi ha cap factura."
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:25
|
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:27
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Invoice %s"
|
msgid "Invoice %s"
|
||||||
msgstr "Factura %s"
|
msgstr "Factura %s"
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:17
|
#: web/template/invoices/view.gohtml:19
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Download invoice"
|
msgid "Download invoice"
|
||||||
msgstr "Descarrega factura"
|
msgstr "Descarrega factura"
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:53
|
#: web/template/invoices/view.gohtml:55
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Concept"
|
msgid "Concept"
|
||||||
msgstr "Concepte"
|
msgstr "Concepte"
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:56
|
#: web/template/invoices/view.gohtml:58
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Discount"
|
msgid "Discount"
|
||||||
msgstr "Descompte"
|
msgstr "Descompte"
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:58
|
#: web/template/invoices/view.gohtml:60
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Units"
|
msgid "Units"
|
||||||
msgstr "Unitats"
|
msgstr "Unitats"
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:93
|
#: web/template/invoices/view.gohtml:95
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Tax Base"
|
msgid "Tax Base"
|
||||||
msgstr "Base imposable"
|
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
|
#: web/template/dashboard.gohtml:2
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
|
@ -428,44 +446,43 @@ msgstr "No podeu deixar la contrasenya en blanc."
|
||||||
msgid "Invalid user or password."
|
msgid "Invalid user or password."
|
||||||
msgstr "Nom d’usuari o contrasenya incorrectes."
|
msgstr "Nom d’usuari o contrasenya incorrectes."
|
||||||
|
|
||||||
#: pkg/products.go:165 pkg/invoices.go:635
|
#: pkg/products.go:165 pkg/invoices.go:643
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nom"
|
msgstr "Nom"
|
||||||
|
|
||||||
#: pkg/products.go:171 pkg/invoices.go:640
|
#: pkg/products.go:171 pkg/invoices.go:648
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripció"
|
msgstr "Descripció"
|
||||||
|
|
||||||
#: pkg/products.go:176 pkg/invoices.go:644
|
#: pkg/products.go:176 pkg/invoices.go:652
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Preu"
|
msgstr "Preu"
|
||||||
|
|
||||||
#: pkg/products.go:186 pkg/invoices.go:670
|
#: pkg/products.go:186 pkg/invoices.go:678
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Taxes"
|
msgid "Taxes"
|
||||||
msgstr "Imposts"
|
msgstr "Imposts"
|
||||||
|
|
||||||
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:532
|
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:717
|
||||||
#: pkg/invoices.go:706
|
|
||||||
msgid "Name can not be empty."
|
msgid "Name can not be empty."
|
||||||
msgstr "No podeu deixar el nom en blanc."
|
msgstr "No podeu deixar el nom en blanc."
|
||||||
|
|
||||||
#: pkg/products.go:207 pkg/invoices.go:707
|
#: pkg/products.go:207 pkg/invoices.go:718
|
||||||
msgid "Price can not be empty."
|
msgid "Price can not be empty."
|
||||||
msgstr "No podeu deixar el preu en blanc."
|
msgstr "No podeu deixar el preu en blanc."
|
||||||
|
|
||||||
#: pkg/products.go:208 pkg/invoices.go:708
|
#: pkg/products.go:208 pkg/invoices.go:719
|
||||||
msgid "Price must be a number greater than zero."
|
msgid "Price must be a number greater than zero."
|
||||||
msgstr "El preu ha de ser un número major a zero."
|
msgstr "El preu ha de ser un número major a zero."
|
||||||
|
|
||||||
#: pkg/products.go:210 pkg/invoices.go:716
|
#: pkg/products.go:210 pkg/invoices.go:727
|
||||||
msgid "Selected tax is not valid."
|
msgid "Selected tax is not valid."
|
||||||
msgstr "Heu seleccionat un impost que no és vàlid."
|
msgstr "Heu seleccionat un impost que no és vàlid."
|
||||||
|
|
||||||
#: pkg/products.go:211 pkg/invoices.go:717
|
#: pkg/products.go:211 pkg/invoices.go:728
|
||||||
msgid "You can only select a tax of each class."
|
msgid "You can only select a tax of each class."
|
||||||
msgstr "Només podeu seleccionar un impost de cada classe."
|
msgstr "Només podeu seleccionar un impost de cada classe."
|
||||||
|
|
||||||
|
@ -573,88 +590,105 @@ msgstr "La confirmació no és igual a la contrasenya."
|
||||||
msgid "Selected language is not valid."
|
msgid "Selected language is not valid."
|
||||||
msgstr "Heu seleccionat un idioma que no és vàlid."
|
msgstr "Heu seleccionat un idioma que no és vàlid."
|
||||||
|
|
||||||
#: pkg/invoices.go:302
|
#: pkg/invoices.go:303
|
||||||
msgid "Select a customer to bill."
|
msgid "Select a customer to bill."
|
||||||
msgstr "Escolliu un client a facturar."
|
msgstr "Escolliu un client a facturar."
|
||||||
|
|
||||||
#: pkg/invoices.go:397 pkg/invoices.go:426
|
#: pkg/invoices.go:402
|
||||||
msgid "Invalid action"
|
|
||||||
msgstr "Acció invàlida."
|
|
||||||
|
|
||||||
#: pkg/invoices.go:420
|
|
||||||
msgid "invoices.zip"
|
msgid "invoices.zip"
|
||||||
msgstr "factures.zip"
|
msgstr "factures.zip"
|
||||||
|
|
||||||
#: pkg/invoices.go:468
|
#: pkg/invoices.go:408 pkg/invoices.go:836
|
||||||
|
msgid "Invalid action"
|
||||||
|
msgstr "Acció invàlida."
|
||||||
|
|
||||||
|
#: pkg/invoices.go:452
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Invoice Status"
|
||||||
|
msgstr "Estat de la factura"
|
||||||
|
|
||||||
|
#: pkg/invoices.go:458
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Customer"
|
msgid "Customer"
|
||||||
msgstr "Client"
|
msgstr "Client"
|
||||||
|
|
||||||
#: pkg/invoices.go:474
|
#: pkg/invoices.go:464
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Number"
|
msgid "Number"
|
||||||
msgstr "Número"
|
msgstr "Número"
|
||||||
|
|
||||||
#: pkg/invoices.go:479
|
#: pkg/invoices.go:469
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Invoice Date"
|
msgid "Invoice Date"
|
||||||
msgstr "Data de factura"
|
msgstr "Data de factura"
|
||||||
|
|
||||||
#: pkg/invoices.go:485
|
#: pkg/invoices.go:475
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Notes"
|
msgid "Notes"
|
||||||
msgstr "Notes"
|
msgstr "Notes"
|
||||||
|
|
||||||
#: pkg/invoices.go:490
|
#: pkg/invoices.go:480
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etiquetes"
|
msgstr "Etiquetes"
|
||||||
|
|
||||||
#: pkg/invoices.go:496
|
#: pkg/invoices.go:486
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Payment Method"
|
msgid "Payment Method"
|
||||||
msgstr "Mètode de pagament"
|
msgstr "Mètode de pagament"
|
||||||
|
|
||||||
#: pkg/invoices.go:533
|
#: 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
|
||||||
msgid "Invoice date can not be empty."
|
msgid "Invoice date can not be empty."
|
||||||
msgstr "No podeu deixar la data de la factura en blanc."
|
msgstr "No podeu deixar la data de la factura en blanc."
|
||||||
|
|
||||||
#: pkg/invoices.go:534
|
#: pkg/invoices.go:526
|
||||||
msgid "Invoice date must be a valid date."
|
msgid "Invoice date must be a valid date."
|
||||||
msgstr "La data de facturació ha de ser vàlida."
|
msgstr "La data de facturació ha de ser vàlida."
|
||||||
|
|
||||||
#: pkg/invoices.go:536
|
#: pkg/invoices.go:528
|
||||||
msgid "Selected payment method is not valid."
|
msgid "Selected payment method is not valid."
|
||||||
msgstr "Heu seleccionat un mètode de pagament que no és vàlid."
|
msgstr "Heu seleccionat un mètode de pagament que no és vàlid."
|
||||||
|
|
||||||
#: pkg/invoices.go:630
|
#: pkg/invoices.go:633 pkg/invoices.go:638
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Id"
|
msgid "Id"
|
||||||
msgstr "Identificador"
|
msgstr "Identificador"
|
||||||
|
|
||||||
#: pkg/invoices.go:653
|
#: pkg/invoices.go:661
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Quantity"
|
msgid "Quantity"
|
||||||
msgstr "Quantitat"
|
msgstr "Quantitat"
|
||||||
|
|
||||||
#: pkg/invoices.go:661
|
#: pkg/invoices.go:669
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Discount (%)"
|
msgid "Discount (%)"
|
||||||
msgstr "Descompte (%)"
|
msgstr "Descompte (%)"
|
||||||
|
|
||||||
#: pkg/invoices.go:710
|
#: pkg/invoices.go:716
|
||||||
|
msgid "Product ID can not be empty."
|
||||||
|
msgstr "No podeu deixar l’identificador del producte en blanc."
|
||||||
|
|
||||||
|
#: pkg/invoices.go:721
|
||||||
msgid "Quantity can not be empty."
|
msgid "Quantity can not be empty."
|
||||||
msgstr "No podeu deixar la quantitat en blanc."
|
msgstr "No podeu deixar la quantitat en blanc."
|
||||||
|
|
||||||
#: pkg/invoices.go:711
|
#: pkg/invoices.go:722
|
||||||
msgid "Quantity must be a number greater than zero."
|
msgid "Quantity must be a number greater than zero."
|
||||||
msgstr "La quantitat ha de ser un número major a zero."
|
msgstr "La quantitat ha de ser un número major a zero."
|
||||||
|
|
||||||
#: pkg/invoices.go:713
|
#: pkg/invoices.go:724
|
||||||
msgid "Discount can not be empty."
|
msgid "Discount can not be empty."
|
||||||
msgstr "No podeu deixar el descompte en blanc."
|
msgstr "No podeu deixar el descompte en blanc."
|
||||||
|
|
||||||
#: pkg/invoices.go:714
|
#: pkg/invoices.go:725
|
||||||
msgid "Discount must be a percentage between 0 and 100."
|
msgid "Discount must be a percentage between 0 and 100."
|
||||||
msgstr "El descompte ha de ser un percentatge entre 0 i 100."
|
msgstr "El descompte ha de ser un percentatge entre 0 i 100."
|
||||||
|
|
||||||
|
|
154
po/es.po
154
po/es.po
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: numerus\n"
|
"Project-Id-Version: numerus\n"
|
||||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||||
"POT-Creation-Date: 2023-03-10 13:59+0100\n"
|
"POT-Creation-Date: 2023-03-13 14:50+0100\n"
|
||||||
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
"PO-Revision-Date: 2023-01-18 17:45+0100\n"
|
||||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||||
|
@ -18,79 +18,82 @@ msgstr ""
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:2
|
#: web/template/invoices/products.gohtml:2
|
||||||
#: web/template/invoices/products.gohtml:15
|
#: web/template/invoices/products.gohtml:19
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Add Products to Invoice"
|
msgid "Add Products to Invoice"
|
||||||
msgstr "Añadir productos a la factura"
|
msgstr "Añadir productos a la factura"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:9 web/template/invoices/new.gohtml:9
|
#: 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/index.gohtml:8 web/template/invoices/view.gohtml:9
|
||||||
#: web/template/contacts/new.gohtml:9 web/template/contacts/index.gohtml:8
|
#: web/template/invoices/edit.gohtml:9 web/template/contacts/new.gohtml:9
|
||||||
#: web/template/contacts/edit.gohtml:9 web/template/profile.gohtml:9
|
#: web/template/contacts/index.gohtml:8 web/template/contacts/edit.gohtml:9
|
||||||
#: web/template/tax-details.gohtml:8 web/template/products/new.gohtml:9
|
#: web/template/profile.gohtml:9 web/template/tax-details.gohtml:8
|
||||||
#: web/template/products/index.gohtml:8 web/template/products/edit.gohtml:9
|
#: web/template/products/new.gohtml:9 web/template/products/index.gohtml:8
|
||||||
|
#: web/template/products/edit.gohtml:9
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Inicio"
|
msgstr "Inicio"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:10 web/template/invoices/new.gohtml:10
|
#: 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/index.gohtml:2 web/template/invoices/index.gohtml:9
|
||||||
#: web/template/invoices/view.gohtml:10
|
#: web/template/invoices/view.gohtml:10 web/template/invoices/edit.gohtml:10
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Invoices"
|
msgid "Invoices"
|
||||||
msgstr "Facturas"
|
msgstr "Facturas"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:11 web/template/invoices/new.gohtml:2
|
#: web/template/invoices/products.gohtml:12 web/template/invoices/new.gohtml:2
|
||||||
#: web/template/invoices/new.gohtml:11 web/template/invoices/new.gohtml:15
|
#: web/template/invoices/new.gohtml:11 web/template/invoices/new.gohtml:15
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "New Invoice"
|
msgid "New Invoice"
|
||||||
msgstr "Nueva factura"
|
msgstr "Nueva factura"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:42
|
#: web/template/invoices/products.gohtml:47
|
||||||
#: web/template/products/index.gohtml:21
|
#: web/template/products/index.gohtml:21
|
||||||
msgctxt "product"
|
msgctxt "product"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Todos"
|
msgstr "Todos"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:43
|
#: web/template/invoices/products.gohtml:48
|
||||||
#: web/template/products/index.gohtml:22
|
#: web/template/products/index.gohtml:22
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nombre"
|
msgstr "Nombre"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:44
|
#: web/template/invoices/products.gohtml:49
|
||||||
#: web/template/invoices/view.gohtml:54 web/template/products/index.gohtml:23
|
#: web/template/invoices/view.gohtml:56 web/template/products/index.gohtml:23
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Precio"
|
msgstr "Precio"
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:58
|
#: web/template/invoices/products.gohtml:63
|
||||||
#: web/template/products/index.gohtml:37
|
#: web/template/products/index.gohtml:37
|
||||||
msgid "No products added yet."
|
msgid "No products added yet."
|
||||||
msgstr "No hay productos."
|
msgstr "No hay productos."
|
||||||
|
|
||||||
#: web/template/invoices/products.gohtml:66 web/template/invoices/new.gohtml:62
|
#: web/template/invoices/products.gohtml:71 web/template/invoices/new.gohtml:63
|
||||||
|
#: web/template/invoices/edit.gohtml:64
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Add products"
|
msgid "Add products"
|
||||||
msgstr "Añadir productos"
|
msgstr "Añadir productos"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:43 web/template/invoices/view.gohtml:59
|
#: web/template/invoices/new.gohtml:44 web/template/invoices/view.gohtml:61
|
||||||
|
#: web/template/invoices/edit.gohtml:45
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Subtotal"
|
msgid "Subtotal"
|
||||||
msgstr "Subtotal"
|
msgstr "Subtotal"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:53 web/template/invoices/view.gohtml:63
|
#: web/template/invoices/new.gohtml:54 web/template/invoices/view.gohtml:65
|
||||||
#: web/template/invoices/view.gohtml:103
|
#: web/template/invoices/view.gohtml:105 web/template/invoices/edit.gohtml:55
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr "Total"
|
msgstr "Total"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:65
|
#: web/template/invoices/new.gohtml:66 web/template/invoices/edit.gohtml:67
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Update"
|
msgid "Update"
|
||||||
msgstr "Actualizar"
|
msgstr "Actualizar"
|
||||||
|
|
||||||
#: web/template/invoices/new.gohtml:67 web/template/invoices/index.gohtml:19
|
#: web/template/invoices/new.gohtml:68 web/template/invoices/index.gohtml:19
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "New invoice"
|
msgid "New invoice"
|
||||||
msgstr "Nueva factura"
|
msgstr "Nueva factura"
|
||||||
|
@ -105,7 +108,7 @@ msgctxt "invoice"
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Todas"
|
msgstr "Todas"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:29 web/template/invoices/view.gohtml:26
|
#: web/template/invoices/index.gohtml:29 web/template/invoices/view.gohtml:28
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "Fecha"
|
msgstr "Fecha"
|
||||||
|
@ -150,45 +153,60 @@ msgctxt "action"
|
||||||
msgid "Select invoice %v"
|
msgid "Select invoice %v"
|
||||||
msgstr "Seleccionar factura %v"
|
msgstr "Seleccionar factura %v"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:91 web/template/invoices/view.gohtml:14
|
#: 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
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Duplicate"
|
msgid "Duplicate"
|
||||||
msgstr "Duplicar"
|
msgstr "Duplicar"
|
||||||
|
|
||||||
#: web/template/invoices/index.gohtml:101
|
#: web/template/invoices/index.gohtml:108
|
||||||
msgid "No invoices added yet."
|
msgid "No invoices added yet."
|
||||||
msgstr "No hay facturas."
|
msgstr "No hay facturas."
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:25
|
#: web/template/invoices/view.gohtml:2 web/template/invoices/view.gohtml:27
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Invoice %s"
|
msgid "Invoice %s"
|
||||||
msgstr "Factura %s"
|
msgstr "Factura %s"
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:17
|
#: web/template/invoices/view.gohtml:19
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Download invoice"
|
msgid "Download invoice"
|
||||||
msgstr "Descargar factura"
|
msgstr "Descargar factura"
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:53
|
#: web/template/invoices/view.gohtml:55
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Concept"
|
msgid "Concept"
|
||||||
msgstr "Concepto"
|
msgstr "Concepto"
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:56
|
#: web/template/invoices/view.gohtml:58
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Discount"
|
msgid "Discount"
|
||||||
msgstr "Descuento"
|
msgstr "Descuento"
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:58
|
#: web/template/invoices/view.gohtml:60
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Units"
|
msgid "Units"
|
||||||
msgstr "Unidades"
|
msgstr "Unidades"
|
||||||
|
|
||||||
#: web/template/invoices/view.gohtml:93
|
#: web/template/invoices/view.gohtml:95
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Tax Base"
|
msgid "Tax Base"
|
||||||
msgstr "Base imponible"
|
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
|
#: web/template/dashboard.gohtml:2
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
|
@ -428,44 +446,43 @@ msgstr "No podéis dejar la contraseña en blanco."
|
||||||
msgid "Invalid user or password."
|
msgid "Invalid user or password."
|
||||||
msgstr "Nombre de usuario o contraseña inválido."
|
msgstr "Nombre de usuario o contraseña inválido."
|
||||||
|
|
||||||
#: pkg/products.go:165 pkg/invoices.go:635
|
#: pkg/products.go:165 pkg/invoices.go:643
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nombre"
|
msgstr "Nombre"
|
||||||
|
|
||||||
#: pkg/products.go:171 pkg/invoices.go:640
|
#: pkg/products.go:171 pkg/invoices.go:648
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripción"
|
msgstr "Descripción"
|
||||||
|
|
||||||
#: pkg/products.go:176 pkg/invoices.go:644
|
#: pkg/products.go:176 pkg/invoices.go:652
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Precio"
|
msgstr "Precio"
|
||||||
|
|
||||||
#: pkg/products.go:186 pkg/invoices.go:670
|
#: pkg/products.go:186 pkg/invoices.go:678
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Taxes"
|
msgid "Taxes"
|
||||||
msgstr "Impuestos"
|
msgstr "Impuestos"
|
||||||
|
|
||||||
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:532
|
#: pkg/products.go:206 pkg/profile.go:92 pkg/invoices.go:717
|
||||||
#: pkg/invoices.go:706
|
|
||||||
msgid "Name can not be empty."
|
msgid "Name can not be empty."
|
||||||
msgstr "No podéis dejar el nombre en blanco."
|
msgstr "No podéis dejar el nombre en blanco."
|
||||||
|
|
||||||
#: pkg/products.go:207 pkg/invoices.go:707
|
#: pkg/products.go:207 pkg/invoices.go:718
|
||||||
msgid "Price can not be empty."
|
msgid "Price can not be empty."
|
||||||
msgstr "No podéis dejar el precio en blanco."
|
msgstr "No podéis dejar el precio en blanco."
|
||||||
|
|
||||||
#: pkg/products.go:208 pkg/invoices.go:708
|
#: pkg/products.go:208 pkg/invoices.go:719
|
||||||
msgid "Price must be a number greater than zero."
|
msgid "Price must be a number greater than zero."
|
||||||
msgstr "El precio tiene que ser un número mayor a cero."
|
msgstr "El precio tiene que ser un número mayor a cero."
|
||||||
|
|
||||||
#: pkg/products.go:210 pkg/invoices.go:716
|
#: pkg/products.go:210 pkg/invoices.go:727
|
||||||
msgid "Selected tax is not valid."
|
msgid "Selected tax is not valid."
|
||||||
msgstr "Habéis escogido un impuesto que no es válido."
|
msgstr "Habéis escogido un impuesto que no es válido."
|
||||||
|
|
||||||
#: pkg/products.go:211 pkg/invoices.go:717
|
#: pkg/products.go:211 pkg/invoices.go:728
|
||||||
msgid "You can only select a tax of each class."
|
msgid "You can only select a tax of each class."
|
||||||
msgstr "Solo podéis escoger un impuesto de cada clase."
|
msgstr "Solo podéis escoger un impuesto de cada clase."
|
||||||
|
|
||||||
|
@ -573,88 +590,105 @@ msgstr "La confirmación no corresponde con la contraseña."
|
||||||
msgid "Selected language is not valid."
|
msgid "Selected language is not valid."
|
||||||
msgstr "Habéis escogido un idioma que no es válido."
|
msgstr "Habéis escogido un idioma que no es válido."
|
||||||
|
|
||||||
#: pkg/invoices.go:302
|
#: pkg/invoices.go:303
|
||||||
msgid "Select a customer to bill."
|
msgid "Select a customer to bill."
|
||||||
msgstr "Escoged un cliente a facturar."
|
msgstr "Escoged un cliente a facturar."
|
||||||
|
|
||||||
#: pkg/invoices.go:397 pkg/invoices.go:426
|
#: pkg/invoices.go:402
|
||||||
msgid "Invalid action"
|
|
||||||
msgstr "Acción inválida."
|
|
||||||
|
|
||||||
#: pkg/invoices.go:420
|
|
||||||
msgid "invoices.zip"
|
msgid "invoices.zip"
|
||||||
msgstr "facturas.zip"
|
msgstr "facturas.zip"
|
||||||
|
|
||||||
#: pkg/invoices.go:468
|
#: pkg/invoices.go:408 pkg/invoices.go:836
|
||||||
|
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:458
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Customer"
|
msgid "Customer"
|
||||||
msgstr "Cliente"
|
msgstr "Cliente"
|
||||||
|
|
||||||
#: pkg/invoices.go:474
|
#: pkg/invoices.go:464
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Number"
|
msgid "Number"
|
||||||
msgstr "Número"
|
msgstr "Número"
|
||||||
|
|
||||||
#: pkg/invoices.go:479
|
#: pkg/invoices.go:469
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Invoice Date"
|
msgid "Invoice Date"
|
||||||
msgstr "Fecha de factura"
|
msgstr "Fecha de factura"
|
||||||
|
|
||||||
#: pkg/invoices.go:485
|
#: pkg/invoices.go:475
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Notes"
|
msgid "Notes"
|
||||||
msgstr "Notas"
|
msgstr "Notas"
|
||||||
|
|
||||||
#: pkg/invoices.go:490
|
#: pkg/invoices.go:480
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etiquetes"
|
msgstr "Etiquetes"
|
||||||
|
|
||||||
#: pkg/invoices.go:496
|
#: pkg/invoices.go:486
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Payment Method"
|
msgid "Payment Method"
|
||||||
msgstr "Método de pago"
|
msgstr "Método de pago"
|
||||||
|
|
||||||
#: pkg/invoices.go:533
|
#: 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
|
||||||
msgid "Invoice date can not be empty."
|
msgid "Invoice date can not be empty."
|
||||||
msgstr "No podéis dejar la fecha de la factura en blanco."
|
msgstr "No podéis dejar la fecha de la factura en blanco."
|
||||||
|
|
||||||
#: pkg/invoices.go:534
|
#: pkg/invoices.go:526
|
||||||
msgid "Invoice date must be a valid date."
|
msgid "Invoice date must be a valid date."
|
||||||
msgstr "La fecha de factura debe ser válida."
|
msgstr "La fecha de factura debe ser válida."
|
||||||
|
|
||||||
#: pkg/invoices.go:536
|
#: pkg/invoices.go:528
|
||||||
msgid "Selected payment method is not valid."
|
msgid "Selected payment method is not valid."
|
||||||
msgstr "Habéis escogido un método de pago que no es válido."
|
msgstr "Habéis escogido un método de pago que no es válido."
|
||||||
|
|
||||||
#: pkg/invoices.go:630
|
#: pkg/invoices.go:633 pkg/invoices.go:638
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Id"
|
msgid "Id"
|
||||||
msgstr "Identificador"
|
msgstr "Identificador"
|
||||||
|
|
||||||
#: pkg/invoices.go:653
|
#: pkg/invoices.go:661
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Quantity"
|
msgid "Quantity"
|
||||||
msgstr "Cantidad"
|
msgstr "Cantidad"
|
||||||
|
|
||||||
#: pkg/invoices.go:661
|
#: pkg/invoices.go:669
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Discount (%)"
|
msgid "Discount (%)"
|
||||||
msgstr "Descuento (%)"
|
msgstr "Descuento (%)"
|
||||||
|
|
||||||
#: pkg/invoices.go:710
|
#: 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
|
||||||
msgid "Quantity can not be empty."
|
msgid "Quantity can not be empty."
|
||||||
msgstr "No podéis dejar la cantidad en blanco."
|
msgstr "No podéis dejar la cantidad en blanco."
|
||||||
|
|
||||||
#: pkg/invoices.go:711
|
#: pkg/invoices.go:722
|
||||||
msgid "Quantity must be a number greater than zero."
|
msgid "Quantity must be a number greater than zero."
|
||||||
msgstr "La cantidad tiene que ser un número mayor a cero."
|
msgstr "La cantidad tiene que ser un número mayor a cero."
|
||||||
|
|
||||||
#: pkg/invoices.go:713
|
#: pkg/invoices.go:724
|
||||||
msgid "Discount can not be empty."
|
msgid "Discount can not be empty."
|
||||||
msgstr "No podéis dejar el descuento en blanco."
|
msgstr "No podéis dejar el descuento en blanco."
|
||||||
|
|
||||||
#: pkg/invoices.go:714
|
#: pkg/invoices.go:725
|
||||||
msgid "Discount must be a percentage between 0 and 100."
|
msgid "Discount must be a percentage between 0 and 100."
|
||||||
msgstr "El descuento tiene que ser un porcentaje entre 0 y 100."
|
msgstr "El descuento tiene que ser un porcentaje entre 0 y 100."
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
{{ 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 }}
|
|
@ -54,12 +54,13 @@
|
||||||
<form action="{{companyURI "/invoices/"}}{{ .Slug }}" method="POST">
|
<form action="{{companyURI "/invoices/"}}{{ .Slug }}" method="POST">
|
||||||
{{ csrfToken }}
|
{{ csrfToken }}
|
||||||
{{ putMethod }}
|
{{ putMethod }}
|
||||||
|
<input type="hidden" name="quick" value="status">
|
||||||
<ul role="menu">
|
<ul role="menu">
|
||||||
{{- range $status, $name := $.InvoiceStatuses }}
|
{{- range $status, $name := $.InvoiceStatuses }}
|
||||||
{{- if ne $status $invoice.Status }}
|
{{- if ne $status $invoice.Status }}
|
||||||
<li role="presentation">
|
<li role="presentation">
|
||||||
<button role="menuitem" type="submit"
|
<button role="menuitem" type="submit"
|
||||||
name="status" value="{{ $status }}"
|
name="invoice_status" value="{{ $status }}"
|
||||||
class="invoice-status-{{ $status }}"
|
class="invoice-status-{{ $status }}"
|
||||||
>{{ $name }}</button>
|
>{{ $name }}</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -85,6 +86,12 @@
|
||||||
<details class="menu">
|
<details class="menu">
|
||||||
<summary><i class="ri-more-line"></i></summary>
|
<summary><i class="ri-more-line"></i></summary>
|
||||||
<ul role="menu" class="action-menu">
|
<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">
|
<li role="presentation">
|
||||||
<a role="menuitem" href="{{ companyURI "/invoices/new"}}?duplicate={{ .Slug }}">
|
<a role="menuitem" href="{{ companyURI "/invoices/new"}}?duplicate={{ .Slug }}">
|
||||||
<i class="ri-file-copy-line"></i>
|
<i class="ri-file-copy-line"></i>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.invoiceForm*/ -}}
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.newInvoicePage*/ -}}
|
||||||
<nav>
|
<nav>
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
{{ csrfToken }}
|
{{ csrfToken }}
|
||||||
|
|
||||||
{{ with .Form -}}
|
{{ with .Form -}}
|
||||||
|
{{ template "hidden-select-field" .InvoiceStatus }}
|
||||||
{{ template "select-field" .Customer }}
|
{{ template "select-field" .Customer }}
|
||||||
{{ template "input-field" .Number }}
|
{{ template "input-field" .Number }}
|
||||||
{{ template "input-field" .Date }}
|
{{ template "input-field" .Date }}
|
||||||
|
|
|
@ -8,12 +8,16 @@
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||||
<a href="{{ companyURI "/invoices"}}">{{( pgettext "Invoices" "title" )}}</a> /
|
<a href="{{ companyURI "/invoices"}}">{{( pgettext "Invoices" "title" )}}</a> /
|
||||||
|
{{ if eq .Form.Number.Val "" }}
|
||||||
<a>{{( pgettext "New Invoice" "title" )}}</a>
|
<a>{{( pgettext "New Invoice" "title" )}}</a>
|
||||||
|
{{ else }}
|
||||||
|
<a>{{ .Form.Number }}</a>
|
||||||
|
{{ end }}
|
||||||
</p>
|
</p>
|
||||||
</nav>
|
</nav>
|
||||||
<section class="dialog-content">
|
<section class="dialog-content">
|
||||||
<h2>{{(pgettext "Add Products to Invoice" "title")}}</h2>
|
<h2>{{(pgettext "Add Products to Invoice" "title")}}</h2>
|
||||||
<form method="POST" action="{{ companyURI "/invoices/new" }}">
|
<form method="POST" action="{{ .Action }}">
|
||||||
{{ csrfToken }}
|
{{ csrfToken }}
|
||||||
|
|
||||||
{{- with .Form }}
|
{{- with .Form }}
|
||||||
|
@ -25,6 +29,7 @@
|
||||||
|
|
||||||
{{- range $product := .Products }}
|
{{- range $product := .Products }}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
{{ template "hidden-field" .InvoiceProductId }}
|
||||||
{{ template "hidden-field" .ProductId }}
|
{{ template "hidden-field" .ProductId }}
|
||||||
{{ template "hidden-field" .Name }}
|
{{ template "hidden-field" .Name }}
|
||||||
{{ template "hidden-field" .Description }}
|
{{ template "hidden-field" .Description }}
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
<a>{{ .Number }}</a>
|
<a>{{ .Number }}</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a class="primary button" href="{{ companyURI "/invoices/new"}}?duplicate={{ .Slug }}">{{( pgettext "Duplicate" "action" )}}</a>
|
<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"
|
<a class="primary button"
|
||||||
href="{{ companyURI "/invoices/" }}{{ .Slug }}.pdf"
|
href="{{ companyURI "/invoices/" }}{{ .Slug }}.pdf"
|
||||||
download="{{ .Number}}.pdf">{{( pgettext "Download invoice" "action" )}}</a>
|
download="{{ .Number}}.pdf">{{( pgettext "Download invoice" "action" )}}</a>
|
||||||
|
|
Loading…
Reference in New Issue