2023-06-07 14:35:31 +00:00
|
|
|
{{ define "title" -}}
|
|
|
|
{{ .Number | printf ( pgettext "Quotation %s" "title" )}}
|
|
|
|
{{- end }}
|
|
|
|
|
|
|
|
{{ define "breadcrumbs" -}}
|
|
|
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.quote*/ -}}
|
|
|
|
<nav>
|
|
|
|
<p data-hx-target="main" data-hx-boost="true">
|
|
|
|
<a href="{{ companyURI "/" }}">{{( pgettext "Home" "title" )}}</a> /
|
|
|
|
<a href="{{ companyURI "/quotes"}}">{{( pgettext "Quotations" "title" )}}</a> /
|
|
|
|
<a>{{ .Number }}</a>
|
|
|
|
</p>
|
|
|
|
<p>
|
2023-06-10 18:46:03 +00:00
|
|
|
<a class="button primary"
|
|
|
|
data-hx-target="main" data-hx-boost="true"
|
|
|
|
href="{{ companyURI "/invoices/new"}}?quote={{ .Slug }}">{{( pgettext "Create invoice" "action" )}}</a>
|
2023-06-07 14:35:31 +00:00
|
|
|
<a class="button primary"
|
|
|
|
data-hx-target="main" data-hx-boost="true"
|
|
|
|
href="{{ companyURI "/quotes/new"}}?duplicate={{ .Slug }}">{{( pgettext "Duplicate" "action" )}}</a>
|
|
|
|
<a class="button primary"
|
|
|
|
data-hx-target="main" data-hx-boost="true"
|
|
|
|
href="{{ companyURI "/quotes/"}}{{ .Slug }}/edit">{{( pgettext "Edit" "action" )}}</a>
|
|
|
|
<a class="primary button"
|
|
|
|
href="{{ companyURI "/quotes/" }}{{ .Slug }}.pdf"
|
|
|
|
download="{{ .Number}}.pdf">{{( pgettext "Download quotation" "action" )}}</a>
|
|
|
|
</p>
|
|
|
|
</nav>
|
|
|
|
{{- end }}
|
|
|
|
|
|
|
|
{{ define "content" }}
|
|
|
|
{{- /*gotype: dev.tandem.ws/tandem/numerus/pkg.quote*/ -}}
|
|
|
|
<link rel="stylesheet" type="text/css" href="/static/invoice.css">
|
|
|
|
<article class="invoice">
|
|
|
|
<header>
|
|
|
|
<div>
|
|
|
|
<h1>{{ .Number | printf ( pgettext "Quotation %s" "title" )}}</h1>
|
|
|
|
<p class="date">{{( pgettext "Date" "title" )}} {{ .Date | formatDate }}</p>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<address class="quoter">
|
|
|
|
{{ .Quoter.Name }}<br>
|
|
|
|
{{ .Quoter.VATIN }}<br>
|
|
|
|
{{ .Quoter.Address }}<br>
|
|
|
|
{{ .Quoter.City }} ({{ .Quoter.PostalCode}}), {{ .Quoter.Province }}<br>
|
|
|
|
{{ .Quoter.Email }}<br>
|
|
|
|
{{ .Quoter.Phone }}<br>
|
|
|
|
</address>
|
|
|
|
|
|
|
|
<p class="legal">{{ .LegalDisclaimer }}</p>
|
|
|
|
</header>
|
|
|
|
|
|
|
|
<div>
|
Allow empty contact and payment method for quotes
I have to use a value to be used as “none” for payment method and
contact. In PL/pgSQL add_quote and edit_quote functions, that value is
NULL, while in forms it is the empty string. I can not simply pass the
empty string for either of these fields because PL/pgSQL expects
(nullable) integers, and "" is not a valid integer and is not NULL
either. A conversion is necessary.
Apparently, Go’s nil is not a valid representation for SQL’s NULL with
pgx, and had to use sql.NullString instead.
I also needed to coalesce contact’s VATIN and phone, because null values
can not be scanned to *string. I did not do that before because
`coalesce(vatin, '')` throws an error that '' is not a valid VATIN and
just left as is, wrongly expecting that pgx would do the job of leaving
the string blank for me. It does not.
Lastly, i can not blindly write Quotee’s tax details in the quote’s view
page, or we would see the (), characters for the empty address info.
2023-06-08 11:05:41 +00:00
|
|
|
{{ if .HasQuotee -}}
|
2023-06-07 14:35:31 +00:00
|
|
|
<address class="quotee">
|
|
|
|
{{ .Quotee.Name }}<br>
|
|
|
|
{{ .Quotee.VATIN }}<br>
|
|
|
|
{{ .Quotee.Address }}<br>
|
|
|
|
{{ .Quotee.City }} ({{ .Quotee.PostalCode}}), {{ .Quotee.Province }}<br>
|
|
|
|
</address>
|
Allow empty contact and payment method for quotes
I have to use a value to be used as “none” for payment method and
contact. In PL/pgSQL add_quote and edit_quote functions, that value is
NULL, while in forms it is the empty string. I can not simply pass the
empty string for either of these fields because PL/pgSQL expects
(nullable) integers, and "" is not a valid integer and is not NULL
either. A conversion is necessary.
Apparently, Go’s nil is not a valid representation for SQL’s NULL with
pgx, and had to use sql.NullString instead.
I also needed to coalesce contact’s VATIN and phone, because null values
can not be scanned to *string. I did not do that before because
`coalesce(vatin, '')` throws an error that '' is not a valid VATIN and
just left as is, wrongly expecting that pgx would do the job of leaving
the string blank for me. It does not.
Lastly, i can not blindly write Quotee’s tax details in the quote’s view
page, or we would see the (), characters for the empty address info.
2023-06-08 11:05:41 +00:00
|
|
|
{{- end }}
|
2023-06-07 14:35:31 +00:00
|
|
|
|
2023-06-08 10:52:10 +00:00
|
|
|
{{ if .TermsAndConditions -}}
|
|
|
|
<p class="terms_and_conditions">{{(gettext "Terms and Conditions:")}} {{ .TermsAndConditions }}</p>
|
|
|
|
{{- end }}
|
|
|
|
|
2023-06-07 14:35:31 +00:00
|
|
|
{{- $columns := 5 | add (len .TaxClasses) | add (boolToInt .HasDiscounts) -}}
|
|
|
|
<table>
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th>{{( pgettext "Concept" "title" )}}</th>
|
|
|
|
<th class="numeric">{{( pgettext "Price" "title" )}}</th>
|
|
|
|
{{ if .HasDiscounts -}}
|
|
|
|
<th class="numeric">{{( pgettext "Discount" "title" )}}</th>
|
|
|
|
{{ end -}}
|
|
|
|
<th class="numeric">{{( pgettext "Units" "title" )}}</th>
|
|
|
|
<th class="numeric">{{( pgettext "Subtotal" "title" )}}</th>
|
|
|
|
{{ range $class := .TaxClasses -}}
|
|
|
|
<th class="numeric">{{ . }}</th>
|
|
|
|
{{ end -}}
|
|
|
|
<th class="numeric">{{( pgettext "Total" "title" )}}</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
{{ $lastIndex := len .Products | sub 1 }}
|
|
|
|
{{ range $index, $product := .Products -}}
|
|
|
|
<tbody>
|
|
|
|
{{- if .Description }}
|
|
|
|
<tr class="name">
|
|
|
|
<td colspan="{{ $columns }}">{{ .Name }}</td>
|
|
|
|
</tr>
|
|
|
|
{{ end -}}
|
|
|
|
<tr>
|
|
|
|
{{- if .Description }}
|
|
|
|
<td>{{ .Description }}</td>
|
|
|
|
{{- else }}
|
|
|
|
<td>{{ .Name }}</td>
|
|
|
|
{{- end -}}
|
|
|
|
<td class="numeric">{{ .Price | formatPrice }}</td>
|
|
|
|
{{ if $.HasDiscounts -}}
|
|
|
|
<td class="numeric">{{ $product.Discount | formatPercent }}</td>
|
|
|
|
{{ end -}}
|
|
|
|
<td class="numeric">{{ .Quantity }}</td>
|
|
|
|
<td class="numeric">{{ .Subtotal | formatPrice }}</td>
|
|
|
|
{{ range $class := $.TaxClasses -}}
|
|
|
|
<td class="numeric">{{ index $product.Taxes $class | formatPercent }}</td>
|
|
|
|
{{ end -}}
|
|
|
|
<td class="numeric">{{ .Total | formatPrice }}</td>
|
|
|
|
</tr>
|
|
|
|
{{ if (eq $index $lastIndex) }}
|
|
|
|
<tr class="tfoot separator">
|
|
|
|
<th scope="row" colspan="{{ $columns | sub 1 }}">{{( pgettext "Tax Base" "title" )}}</th>
|
|
|
|
<td class="numeric">{{ $.Subtotal | formatPrice }}</td>
|
|
|
|
</tr>
|
|
|
|
{{ range $tax := $.Taxes -}}
|
|
|
|
<tr class="tfoot">
|
|
|
|
<th scope="row" colspan="{{ $columns | sub 1 }}">{{ index . 0 }}</th>
|
|
|
|
<td class="numeric">{{ index . 1 | formatPrice }}</td>
|
|
|
|
</tr>
|
|
|
|
{{- end }}
|
|
|
|
<tr class="tfoot">
|
|
|
|
<th scope="row" colspan="{{ $columns | sub 1 }}">{{( pgettext "Total" "title" )}}</th>
|
|
|
|
<td class="numeric">{{ $.Total | formatPrice }}</td>
|
|
|
|
</tr>
|
|
|
|
{{ end }}
|
|
|
|
</tbody>
|
|
|
|
{{- end }}
|
|
|
|
</table>
|
|
|
|
|
|
|
|
{{ if .Notes -}}
|
|
|
|
<p class="notes">{{ .Notes }}</p>
|
|
|
|
{{- end }}
|
|
|
|
<p class="payment-instructions">{{ .PaymentInstructions }}</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</article>
|
|
|
|
{{- end}}
|