Add the bare-bones form for invoices
This commit is contained in:
parent
6bf51d5eeb
commit
5c15b9de20
|
@ -0,0 +1,152 @@
|
|||
package pkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type InvoicesIndexPage struct {
|
||||
}
|
||||
|
||||
func IndexInvoices(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
page := &InvoicesIndexPage{}
|
||||
mustRenderAppTemplate(w, r, "invoices/index.gohtml", page)
|
||||
}
|
||||
|
||||
func GetInvoiceForm(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
locale := getLocale(r)
|
||||
conn := getConn(r)
|
||||
company := mustGetCompany(r)
|
||||
form := newInvoiceForm(r.Context(), conn, locale, company)
|
||||
slug := params[0].Value
|
||||
if slug == "new" {
|
||||
form.Customer.EmptyLabel = gettext("Select a customer to bill.", locale)
|
||||
form.Date.Val = time.Now().Format("2006-01-02")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
mustRenderNewInvoiceForm(w, r, form)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func mustRenderNewInvoiceForm(w http.ResponseWriter, r *http.Request, form *invoiceForm) {
|
||||
mustRenderAppTemplate(w, r, "invoices/new.gohtml", form)
|
||||
}
|
||||
|
||||
func HandleAddInvoice(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
r.ParseForm()
|
||||
w.Write([]byte("OK"))
|
||||
}
|
||||
|
||||
type invoiceProductForm struct {
|
||||
ProductId *InputField
|
||||
Name *InputField
|
||||
Description *InputField
|
||||
Price *InputField
|
||||
Quantity *InputField
|
||||
Discount *InputField
|
||||
Tax *SelectField
|
||||
}
|
||||
|
||||
type invoiceForm struct {
|
||||
locale *Locale
|
||||
company *Company
|
||||
Customer *SelectField
|
||||
Number *InputField
|
||||
Date *InputField
|
||||
Notes *InputField
|
||||
Products []*invoiceProductForm
|
||||
}
|
||||
|
||||
func newInvoiceForm(ctx context.Context, conn *Conn, locale *Locale, company *Company) *invoiceForm {
|
||||
return &invoiceForm{
|
||||
locale: locale,
|
||||
company: company,
|
||||
Customer: &SelectField{
|
||||
Name: "customer",
|
||||
Label: pgettext("input", "Customer", locale),
|
||||
Required: true,
|
||||
Options: MustGetOptions(ctx, conn, "select contact_id::text, business_name from contact where company_id = $1 order by business_name", company.Id),
|
||||
},
|
||||
Number: &InputField{
|
||||
Name: "number",
|
||||
Label: pgettext("input", "Number", locale),
|
||||
Type: "text",
|
||||
Required: false,
|
||||
},
|
||||
Date: &InputField{
|
||||
Name: "date",
|
||||
Label: pgettext("input", "Invoice Date", locale),
|
||||
Type: "date",
|
||||
Required: true,
|
||||
},
|
||||
Notes: &InputField{
|
||||
Name: "description",
|
||||
Label: pgettext("input", "Notes", locale),
|
||||
Type: "textarea",
|
||||
},
|
||||
Products: []*invoiceProductForm{
|
||||
newInvoiceProductForm(ctx, conn, company, locale),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newInvoiceProductForm(ctx context.Context, conn *Conn, company *Company, locale *Locale) *invoiceProductForm {
|
||||
return &invoiceProductForm{
|
||||
ProductId: &InputField{
|
||||
Name: "product[][id]",
|
||||
Label: pgettext("input", "Id", locale),
|
||||
Type: "hidden",
|
||||
Required: true,
|
||||
},
|
||||
Name: &InputField{
|
||||
Name: "product[][name]",
|
||||
Label: pgettext("input", "Name", locale),
|
||||
Type: "text",
|
||||
Required: true,
|
||||
},
|
||||
Description: &InputField{
|
||||
Name: "product[][description]",
|
||||
Label: pgettext("input", "Description", locale),
|
||||
Type: "textarea",
|
||||
},
|
||||
Price: &InputField{
|
||||
Name: "product[][price]",
|
||||
Label: pgettext("input", "Price", locale),
|
||||
Type: "number",
|
||||
Required: true,
|
||||
Attributes: []template.HTMLAttr{
|
||||
`min="0"`,
|
||||
template.HTMLAttr(fmt.Sprintf(`step="%v"`, company.MinCents())),
|
||||
},
|
||||
},
|
||||
Quantity: &InputField{
|
||||
Name: "product[][quantity]",
|
||||
Label: pgettext("input", "Quantity", locale),
|
||||
Type: "number",
|
||||
Required: true,
|
||||
Attributes: []template.HTMLAttr{
|
||||
`min="0"`,
|
||||
},
|
||||
},
|
||||
Discount: &InputField{
|
||||
Name: "product[][discount]",
|
||||
Label: pgettext("input", "Discount (%)", locale),
|
||||
Type: "number",
|
||||
Required: true,
|
||||
Attributes: []template.HTMLAttr{
|
||||
`min="0"`,
|
||||
`max="100"`,
|
||||
},
|
||||
},
|
||||
Tax: &SelectField{
|
||||
Name: "product[][tax]",
|
||||
Label: pgettext("input", "Taxes", locale),
|
||||
Multiple: true,
|
||||
Options: MustGetOptions(ctx, conn, "select tax_id::text, name from tax where company_id = $1 order by name", company.Id),
|
||||
},
|
||||
}
|
||||
}
|
|
@ -22,6 +22,9 @@ func NewRouter(db *Db) http.Handler {
|
|||
companyRouter.POST("/products", HandleAddProduct)
|
||||
companyRouter.GET("/products/:slug", GetProductForm)
|
||||
companyRouter.PUT("/products/:slug", HandleUpdateProduct)
|
||||
companyRouter.GET("/invoices", IndexInvoices)
|
||||
companyRouter.POST("/invoices", HandleAddInvoice)
|
||||
companyRouter.GET("/invoices/:slug", GetInvoiceForm)
|
||||
companyRouter.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||
mustRenderAppTemplate(w, r, "dashboard.gohtml", nil)
|
||||
})
|
||||
|
|
|
@ -304,7 +304,7 @@ main {
|
|||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="password"], input[type="email"], input[type="tel"], input[type="url"], input[type="number"], select, textarea {
|
||||
input[type="text"], input[type="password"], input[type="email"], input[type="tel"], input[type="url"], input[type="number"], input[type="date"], select, textarea {
|
||||
background-color: var(--numerus--background-color);
|
||||
border: 1px solid var(--numerus--color--black);
|
||||
border-radius: 0;
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<nav aria-label="{{( pgettext "Main" "title" )}}">
|
||||
<ul>
|
||||
<li><a href="{{ companyURI "/" }}">{{( pgettext "Dashboard" "nav" )}}</a></li>
|
||||
<li><a href="{{ companyURI "/invoices" }}">{{( pgettext "Invoices" "nav" )}}</a></li>
|
||||
<li><a href="{{ companyURI "/products" }}">{{( pgettext "Products" "nav" )}}</a></li>
|
||||
<li><a href="{{ companyURI "/contacts" }}">{{( pgettext "Contacts" "nav" )}}</a></li>
|
||||
</ul>
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
{{ define "hidden-field" -}}
|
||||
<input type="{{ .Type }}" name="{{ .Name }}" id="{{ .Name }}-field"
|
||||
{{- range $attribute := .Attributes }} {{$attribute}} {{ end }}
|
||||
{{ if .Required }}required="required"{{ end }} value="{{ .Val }}">
|
||||
{{- end }}
|
||||
|
||||
{{ define "input-field" -}}
|
||||
<div class="input {{ if .Errors }}has-errors{{ end }}">
|
||||
{{ if eq .Type "textarea" }}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
{{ define "title" -}}
|
||||
{{( pgettext "Invoices" "title" )}}
|
||||
{{- end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<nav>
|
||||
<p>
|
||||
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||
<a>{{( pgettext "Invoices" "title" )}}</a>
|
||||
</p>
|
||||
<p>
|
||||
<a class="primary button"
|
||||
href="{{ companyURI "/invoices/new" }}">{{( pgettext "New invoice" "action" )}}</a>
|
||||
</p>
|
||||
</nav>
|
||||
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.InvoicesIndexPage*/ -}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{( pgettext "All" "invoice" )}}</th>
|
||||
<th>{{( pgettext "Date" "title" )}}</th>
|
||||
<th>{{( pgettext "Invoice Num." "title" )}}</th>
|
||||
<th>{{( pgettext "Customer" "title" )}}</th>
|
||||
<th>{{( pgettext "Status" "title" )}}</th>
|
||||
<th>{{( pgettext "Label" "title" )}}</th>
|
||||
<th>{{( pgettext "Download" "title" )}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="7">{{( gettext "No invoices added yet." )}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{{- end }}
|
|
@ -0,0 +1,42 @@
|
|||
{{ define "title" -}}
|
||||
{{( pgettext "New Invoice" "title" )}}
|
||||
{{- end }}
|
||||
|
||||
{{ define "content" }}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.invoiceForm*/ -}}
|
||||
<nav>
|
||||
<p>
|
||||
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
||||
<a href="{{ companyURI "/invoices"}}">{{( pgettext "Invoices" "title" )}}</a> /
|
||||
<a>{{( pgettext "New Invoice" "title" )}}</a>
|
||||
</p>
|
||||
</nav>
|
||||
<section class="dialog-content">
|
||||
<h2>{{(pgettext "New Invoice" "title")}}</h2>
|
||||
<form method="POST" action="{{ companyURI "/invoices" }}">
|
||||
{{ csrfToken }}
|
||||
|
||||
{{ template "select-field" .Customer }}
|
||||
{{ template "input-field" .Number }}
|
||||
{{ template "input-field" .Date }}
|
||||
{{ template "input-field" .Notes }}
|
||||
|
||||
{{- range $product := .Products }}
|
||||
<fieldset>
|
||||
{{ template "hidden-field" .ProductId }}
|
||||
{{ template "input-field" .Name }}
|
||||
{{ template "input-field" .Description }}
|
||||
{{ template "input-field" .Price }}
|
||||
{{ template "input-field" .Quantity }}
|
||||
{{ template "input-field" .Discount }}
|
||||
{{ template "select-field" .Tax }}
|
||||
</fieldset>
|
||||
{{- end }}
|
||||
|
||||
<fieldset>
|
||||
<button class="primary" name="action" value="add" type="submit">{{( pgettext "New invoice" "action" )}}</button>
|
||||
</fieldset>
|
||||
|
||||
</form>
|
||||
</section>
|
||||
{{- end }}
|
Loading…
Reference in New Issue