Compare commits
4 Commits
dfdc9fde76
...
8efae0485e
Author | SHA1 | Date |
---|---|---|
jordi fita mas | 8efae0485e | |
jordi fita mas | 1ab48d2947 | |
jordi fita mas | c685fc496b | |
jordi fita mas | c882158da3 |
|
@ -20,6 +20,7 @@ $$
|
||||||
declare
|
declare
|
||||||
iid integer;
|
iid integer;
|
||||||
products_to_keep integer[];
|
products_to_keep integer[];
|
||||||
|
products_to_delete integer[];
|
||||||
company integer;
|
company integer;
|
||||||
ccode text;
|
ccode text;
|
||||||
product edited_invoice_product;
|
product edited_invoice_product;
|
||||||
|
@ -78,7 +79,16 @@ begin
|
||||||
join unnest(product.tax) as ptax(tax_id) using (tax_id);
|
join unnest(product.tax) as ptax(tax_id) using (tax_id);
|
||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
delete from invoice_product where invoice_id = iid and not (invoice_product_id = any(products_to_keep));
|
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_tag where invoice_id = iid;
|
delete from invoice_tag where invoice_id = iid;
|
||||||
|
|
||||||
|
|
17
pkg/form.go
17
pkg/form.go
|
@ -35,7 +35,18 @@ func (field *InputField) Scan(value interface{}) error {
|
||||||
field.Val = ""
|
field.Val = ""
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
field.Val = fmt.Sprintf("%v", value)
|
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)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +74,10 @@ func (field *InputField) Float64() float64 {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (field *InputField) String() string {
|
||||||
|
return field.Val
|
||||||
|
}
|
||||||
|
|
||||||
type SelectOption struct {
|
type SelectOption struct {
|
||||||
Value string
|
Value string
|
||||||
Label string
|
Label string
|
||||||
|
|
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."
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,8 @@ values (19, 15, 7, 'P1.0', 1100)
|
||||||
;
|
;
|
||||||
|
|
||||||
insert into invoice_product_tax (invoice_product_id, tax_id, tax_rate)
|
insert into invoice_product_tax (invoice_product_id, tax_id, tax_rate)
|
||||||
values (20, 4, 0.21)
|
values (19, 4, 0.21)
|
||||||
|
, (20, 4, 0.21)
|
||||||
, (21, 3, -0.07)
|
, (21, 3, -0.07)
|
||||||
, (21, 4, 0.21)
|
, (21, 4, 0.21)
|
||||||
, (22, 3, -0.15)
|
, (22, 3, -0.15)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{ define "title" -}}
|
{{ define "title" -}}
|
||||||
{{printf (pgettext "Edit Product “%s”" "title") .Name.Val }}
|
{{printf (pgettext "Edit Product “%s”" "title") .Name }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
|
@ -8,11 +8,11 @@
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||||
<a href="{{ companyURI "/products"}}">{{( pgettext "Products" "title" )}}</a> /
|
<a href="{{ companyURI "/products"}}">{{( pgettext "Products" "title" )}}</a> /
|
||||||
<a>{{ .Name.Val }}</a>
|
<a>{{ .Name }}</a>
|
||||||
</p>
|
</p>
|
||||||
</nav>
|
</nav>
|
||||||
<section class="dialog-content">
|
<section class="dialog-content">
|
||||||
<h2>{{printf (pgettext "Edit Product “%s”" "title") .Name.Val }}</h2>
|
<h2>{{printf (pgettext "Edit Product “%s”" "title") .Name }}</h2>
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
{{ csrfToken }}
|
{{ csrfToken }}
|
||||||
{{ putMethod }}
|
{{ putMethod }}
|
||||||
|
|
Loading…
Reference in New Issue