1
0
mirror of https://github.com/bitwarden/server.git synced 2025-01-25 22:21:38 +01:00

invoice pdf generation api

This commit is contained in:
Kyle Spearrin 2017-10-25 00:45:11 -04:00
parent c9d6a7b2c0
commit e7b565d007
5 changed files with 265 additions and 0 deletions

View File

@ -13,6 +13,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="jsreport.AspNetCore" Version="1.0.0" />
<PackageReference Include="jsreport.Binary" Version="1.8.2" />
<PackageReference Include="jsreport.Local" Version="1.0.3" />
<PackageReference Include="System.Net.Http" Version="4.3.3" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />

View File

@ -12,6 +12,10 @@ using Microsoft.AspNetCore.Identity;
using Bit.Core.Models.Table;
using Bit.Api.Utilities;
using Bit.Core.Models.Business;
using jsreport.AspNetCore;
using jsreport.Types;
using Bit.Api.Models;
using Stripe;
namespace Bit.Api.Controllers
{
@ -94,6 +98,41 @@ namespace Bit.Api.Controllers
}
}
[HttpGet("{id}/billing-invoice/{invoiceId}")]
[SelfHosted(NotSelfHostedOnly = true)]
[MiddlewareFilter(typeof(JsReportPipeline))]
public async Task<IActionResult> GetBillingInvoice(string id, string invoiceId)
{
var orgIdGuid = new Guid(id);
if(!_currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if(organization == null)
{
throw new NotFoundException();
}
try
{
var invoice = await new StripeInvoiceService().GetAsync(invoiceId);
if(invoice == null || invoice.CustomerId != organization.GatewayCustomerId)
{
throw new NotFoundException();
}
var model = new InvoiceModel(organization, invoice);
HttpContext.JsReportFeature().Recipe(Recipe.PhantomPdf);
return View("Invoice", model);
}
catch(StripeException)
{
throw new NotFoundException();
}
}
[HttpGet("{id}/license")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<OrganizationLicense> GetLicense(string id, [FromQuery]Guid installationId)

View File

@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Linq;
using Bit.Core.Models.Table;
using Stripe;
namespace Bit.Api.Models
{
public class InvoiceModel
{
public InvoiceModel(Organization organization, StripeInvoice invoice)
{
// TODO: address
OurAddress1 = "567 Green St";
OurAddress2 = "Jacksonville, FL 32256";
OurAddress3 = "United States";
CustomerName = organization.BusinessName ?? "--";
// TODO: address and vat
CustomerAddress1 = "123 Any St";
CustomerAddress2 = "New York, NY 10001";
CustomerAddress3 = "United States";
CustomerVatNumber = "PT 123456789";
InvoiceDate = invoice.Date?.ToLongDateString();
InvoiceDueDate = invoice.DueDate?.ToLongDateString();
InvoiceNumber = invoice.Id;
Items = invoice.StripeInvoiceLineItems.Select(i => new Item(i));
SubtotalAmount = (invoice.Total / 100).ToString("C");
VatTotalAmount = 0.ToString("C");
TotalAmount = SubtotalAmount;
Paid = invoice.Paid;
}
public string OurAddress1 { get; set; }
public string OurAddress2 { get; set; }
public string OurAddress3 { get; set; }
public string InvoiceDate { get; set; }
public string InvoiceDueDate { get; set; }
public string InvoiceNumber { get; set; }
public string CustomerName { get; set; }
public string CustomerVatNumber { get; set; }
public string CustomerAddress1 { get; set; }
public string CustomerAddress2 { get; set; }
public string CustomerAddress3 { get; set; }
public IEnumerable<Item> Items { get; set; }
public string SubtotalAmount { get; set; }
public string VatTotalAmount { get; set; }
public string TotalAmount { get; set; }
public bool Paid { get; set; }
public bool UsesVat => !string.IsNullOrWhiteSpace(CustomerVatNumber);
public class Item
{
public Item(StripeInvoiceLineItem item)
{
Quantity = item.Quantity?.ToString() ?? "-";
Amount = (item.Amount / 100).ToString("F");
Description = item.Description ?? "--";
}
public string Description { get; set; }
public string Quantity { get; set; }
public string Amount { get; set; }
}
}
}

View File

@ -20,6 +20,7 @@ using Stripe;
using Bit.Core.Utilities;
using IdentityModel;
using IdentityServer4.AccessTokenValidation;
using jsreport.AspNetCore;
namespace Bit.Api
{
@ -140,6 +141,16 @@ namespace Bit.Api
jsonFormatter.SupportedMediaTypes.Add(textPlainMediaType);
}
}).AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
// PDF generation
if(!globalSettings.SelfHosted)
{
services
.AddJsReport(new jsreport.Local.LocalReporting()
.UseBinary(jsreport.Binary.JsReportBinary.GetBinary())
.AsUtility()
.Create());
}
}
public void Configure(

View File

@ -0,0 +1,145 @@
@model Bit.Api.Models.InvoiceModel
<html>
<head>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 18px;
}
h1, h2, h3, h4 {
font-family: Arial, Helvetica, sans-serif;
}
table {
width: 100%;
}
table th, table td {
text-align: left;
padding: 5px 0;
vertical-align: top;
}
table.items {
border-collapse: collapse;
border: 1px solid black;
}
table.items th, table.items td {
padding: 5px;
border: 1px solid darkgray;
}
.right {
text-align: right;
}
.paid {
margin-top: 50px;
font-size: 60px;
text-align: center;
color: darkgreen;
}
.number {
float: right;
text-align: right;
font-family: Courier New, Courier, monospace;
}
</style>
</head>
<body>
<div class="number">@Model.InvoiceNumber</div>
<h1>INVOICE</h1>
<p>
@if(!string.IsNullOrWhiteSpace(Model.InvoiceDate))
{
<b>Date:</b> @Model.InvoiceDate<br />
}
@if(!string.IsNullOrWhiteSpace(Model.InvoiceDueDate))
{
<b>Due Date:</b> @Model.InvoiceDueDate
}
</p>
<table>
<tbody>
<tr>
<td width="50%">
<h2>From</h2>
<p>
<b>8bit Solutions LLC</b><br />
@Model.OurAddress1<br />
@Model.OurAddress2<br />
@Model.OurAddress3
</p>
</td>
<td width="50%">
<h2>To</h2>
<p>
<b>@Model.CustomerName</b><br />
@if(Model.UsesVat)
{
@:VAT @Model.CustomerVatNumber<br />
}
@if(!string.IsNullOrWhiteSpace(Model.CustomerAddress1))
{
@Model.CustomerAddress1<br />
}
@if(!string.IsNullOrWhiteSpace(Model.CustomerAddress2))
{
@Model.CustomerAddress2<br />
}
@if(!string.IsNullOrWhiteSpace(Model.CustomerAddress3))
{
@Model.CustomerAddress3
}
</p>
</td>
</tr>
</tbody>
</table>
<h2>Items</h2>
<table class="items">
<thead>
<tr>
<th>Description</th>
<th class="right" style="width: 100px;">Qty</th>
@if(Model.UsesVat)
{
<th class="right" style="width: 100px;">VAT %</th>
<th class="right" style="width: 100px;">VAT</th>
}
<th class="right" style="width: 100px;">Total</th>
</tr>
</thead>
<tbody>
@foreach(var item in Model.Items)
{
<tr>
<td>@item.Description</td>
<td class="right">@item.Quantity</td>
@if(Model.UsesVat)
{
<td class="right">0</td>
<td class="right">0.00</td>
}
<td class="right">@item.Amount</td>
</tr>
}
</tbody>
</table>
<h2>Totals</h2>
<b>Subtotal:</b> @Model.SubtotalAmount<br />
@if(Model.UsesVat)
{
<b>Total VAT:</b> @Model.VatTotalAmount<br />
}
<br />
<b>Total:</b> USD @Model.TotalAmount
@if(Model.Paid)
{
<div class="paid">PAID</div>
}
</body>
</html>