parent
3d22cb0f8d
commit
8c67d3a0c4
|
@ -1,6 +1,8 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"code.tnxs.net/taxnexus/lib/api/crm/crm_models"
|
||||
"code.tnxs.net/taxnexus/lib/api/ops/ops_models"
|
||||
)
|
||||
|
@ -16,6 +18,21 @@ type Address struct {
|
|||
Street string
|
||||
}
|
||||
|
||||
// ToString returns the full address in a string
|
||||
func (obj *Address) ToString() string {
|
||||
return obj.Street + " " +
|
||||
obj.City + " " +
|
||||
obj.State + " " +
|
||||
obj.PostalCode
|
||||
}
|
||||
|
||||
// MarshalToJSON returns a JSON string representation of the address
|
||||
func (obj *Address) MarshalToJSON() *string {
|
||||
bytes, _ := json.Marshal(obj)
|
||||
str := string(bytes)
|
||||
return &str
|
||||
}
|
||||
|
||||
// MarshalToCrm converts a first class object to swagger
|
||||
func (obj *Address) MarshalToCrm() *crm_models.Address {
|
||||
if obj == nil {
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package app
|
||||
|
||||
import "sync"
|
||||
|
||||
var contactCache = contactCacheType{
|
||||
obj: map[string]*Contact{},
|
||||
}
|
||||
|
||||
type contactCacheType struct {
|
||||
sync.RWMutex
|
||||
obj map[string]*Contact
|
||||
}
|
||||
|
||||
func (m *contactCacheType) get(recordID string) (*Contact, bool) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
r, ok := m.obj[recordID]
|
||||
return r, ok
|
||||
}
|
||||
|
||||
func (m *contactCacheType) put(recordID string, itm *Contact) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
m.obj[recordID] = itm
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.tnxs.net/taxnexus/lib/api/crm/crm_client/contacts"
|
||||
)
|
||||
|
||||
// GetContact is a first class object retrieval function
|
||||
func GetContact(id string, principal *User) Contact {
|
||||
if id == "" {
|
||||
return Contact{}
|
||||
}
|
||||
c, ok := contactCache.get(id)
|
||||
if ok {
|
||||
return *c
|
||||
}
|
||||
c, err := GetContactByID(id, principal)
|
||||
if err != nil {
|
||||
return Contact{}
|
||||
}
|
||||
return *c
|
||||
}
|
||||
|
||||
// GetContactByID is a first class object retrieval function
|
||||
func GetContactByID(key string, principal *User) (*Contact, error) {
|
||||
sugar.Debugf("render.getContactByID: 📥")
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("render.getContactByID: 💣 ⛔ key is blank")
|
||||
}
|
||||
obj, ok := contactCache.get(key)
|
||||
if ok {
|
||||
sugar.Debugf("render.getContactByID: 👍 🎯 📤")
|
||||
return obj, nil
|
||||
}
|
||||
params := contacts.NewGetContactsParams()
|
||||
params.ContactID = &key
|
||||
response, err := crmClient.Contacts.GetContacts(params, principal.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var newObj *Contact
|
||||
for _, itm := range response.Payload.Data { // single iteration execution
|
||||
newObj = UnMarshalContact(itm)
|
||||
}
|
||||
contactCache.put(key, newObj)
|
||||
sugar.Debugf("render.getContactByID: 👍 🆕 📤")
|
||||
return newObj, nil
|
||||
}
|
|
@ -1,8 +1,58 @@
|
|||
package app
|
||||
|
||||
import "code.tnxs.net/taxnexus/lib/api/geo/geo_models"
|
||||
import (
|
||||
"encoding/base64"
|
||||
"html/template"
|
||||
|
||||
// Coordinate is never ingested, hence no UnMarshal method
|
||||
"code.tnxs.net/taxnexus/lib/api/geo/geo_models"
|
||||
)
|
||||
|
||||
func UnMarshalSwaggerCoordinate(s *geo_models.Coordinate, principal *User) *Coordinate {
|
||||
taxTypes := []*TaxType{}
|
||||
for _, itm := range s.TaxTypes {
|
||||
if itm.ID != "" {
|
||||
taxTypes = append(taxTypes, UnMarshalTaxType(itm))
|
||||
}
|
||||
}
|
||||
var situs string
|
||||
if s.IsDistrict {
|
||||
situs = s.State + " / " + s.County + " / " + s.Place
|
||||
} else {
|
||||
situs = s.State + " / " + s.County
|
||||
}
|
||||
return &Coordinate{
|
||||
ID: s.ID,
|
||||
BusinessTaxTypes: GetBusinessTaxTypes(taxTypes),
|
||||
CannabisTypes: GetCannabisTaxTypes(taxTypes),
|
||||
Country: s.Country,
|
||||
CountryID: s.CountryID,
|
||||
County: s.County,
|
||||
CountyID: s.CountyID,
|
||||
ExciseTaxTypes: GetExciseTaxTypes(taxTypes),
|
||||
Focus: s.Focus,
|
||||
FormattedAddress: s.FormattedAddress,
|
||||
IsDistrict: s.IsDistrict,
|
||||
Latitude: s.Latitude,
|
||||
Longitude: s.Longitude,
|
||||
Map: template.URL(base64.RawStdEncoding.EncodeToString(s.Map)), //nolint:gosec // it definitely is
|
||||
MerchTaxTypes: GetMerchTaxTypes(taxTypes),
|
||||
Name: s.Name,
|
||||
Neighborhood: s.Neighborhood,
|
||||
Place: s.Place,
|
||||
Geocode: s.PlaceGeocode,
|
||||
PlaceID: s.PlaceID,
|
||||
PostalCode: s.PostalCode,
|
||||
Ref: s.Ref,
|
||||
Situs: situs,
|
||||
State: s.State,
|
||||
StateID: s.StateID,
|
||||
Status: s.Status,
|
||||
Street: s.Street,
|
||||
StreetNumber: s.StreetNumber,
|
||||
StreetView: template.URL(base64.RawStdEncoding.EncodeToString(s.StreetView)), //nolint:gosec,lll // it definitely is
|
||||
TelecomTaxTypes: GetTelecomTaxTypes(taxTypes),
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalToSwagger encodes a first class object to swagger
|
||||
func (obj *Coordinate) MarshalToSwagger() *geo_models.Coordinate {
|
||||
|
@ -33,21 +83,21 @@ func (obj *Coordinate) MarshalToSwagger() *geo_models.Coordinate {
|
|||
IsDistrict: obj.IsDistrict,
|
||||
Latitude: obj.Latitude,
|
||||
Longitude: obj.Longitude,
|
||||
Map: obj.Map,
|
||||
Name: obj.Name,
|
||||
Neighborhood: obj.Neighborhood,
|
||||
Place: obj.Place,
|
||||
PlaceGeocode: obj.Geocode,
|
||||
PlaceID: obj.PlaceID,
|
||||
PostalCode: obj.PostalCode,
|
||||
Ref: obj.Ref,
|
||||
State: obj.State,
|
||||
StateID: obj.StateID,
|
||||
Status: obj.Status,
|
||||
Street: obj.Street,
|
||||
StreetNumber: obj.StreetNumber,
|
||||
StreetView: obj.StreetView,
|
||||
TaxTypes: theTaxTypes,
|
||||
TaxRate: &taxRate,
|
||||
// Map: obj.Map,
|
||||
Name: obj.Name,
|
||||
Neighborhood: obj.Neighborhood,
|
||||
Place: obj.Place,
|
||||
PlaceGeocode: obj.Geocode,
|
||||
PlaceID: obj.PlaceID,
|
||||
PostalCode: obj.PostalCode,
|
||||
Ref: obj.Ref,
|
||||
State: obj.State,
|
||||
StateID: obj.StateID,
|
||||
Status: obj.Status,
|
||||
Street: obj.Street,
|
||||
StreetNumber: obj.StreetNumber,
|
||||
// StreetView: obj.StreetView,
|
||||
TaxTypes: theTaxTypes,
|
||||
TaxRate: &taxRate,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package app
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"html/template"
|
||||
|
||||
"code.tnxs.net/taxnexus/lib/api/geo/geo_models"
|
||||
)
|
||||
|
@ -40,12 +41,15 @@ type CoordinateActivityWrapper struct {
|
|||
// Coordinate is a first class object type
|
||||
type Coordinate struct {
|
||||
ID string
|
||||
BusinessTaxTypes []*TaxType
|
||||
CannabisTypes []*TaxType
|
||||
Country string
|
||||
CountryID string
|
||||
County string
|
||||
CountyID string
|
||||
CreatedByID string
|
||||
CreatedDate sql.NullTime
|
||||
ExciseTaxTypes []*TaxType
|
||||
Focus string
|
||||
FormattedAddress string
|
||||
Geocode string
|
||||
|
@ -54,21 +58,24 @@ type Coordinate struct {
|
|||
LastModifiedDate sql.NullTime
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
Map template.URL
|
||||
MerchTaxTypes []*TaxType
|
||||
Name string
|
||||
Neighborhood string
|
||||
Map []byte
|
||||
OwnerID string
|
||||
Place string
|
||||
PlaceID string
|
||||
PostalCode string
|
||||
Ref string
|
||||
Situs string
|
||||
State string
|
||||
StateID string
|
||||
Status string
|
||||
Street string
|
||||
StreetNumber string
|
||||
StreetView []byte
|
||||
StreetView template.URL
|
||||
TaxRate *TaxRate
|
||||
TaxTypes []*TaxType
|
||||
TaxTypeIDs []*string
|
||||
TaxTypes []*TaxType
|
||||
TelecomTaxTypes []*TaxType
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package app
|
||||
|
||||
// Document is an object type used for rendering
|
||||
type Document struct {
|
||||
ID string
|
||||
Filename string
|
||||
HTML string
|
||||
ParentID string
|
||||
PDF string
|
||||
SagaType string
|
||||
Title string
|
||||
URI string
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
package app
|
||||
|
||||
/*
|
||||
|
||||
Author: https://github.com/gorhill
|
||||
Source: https://gist.github.com/gorhill/5285193
|
||||
|
||||
A Go function to render a number to a string based on
|
||||
the following user-specified criteria:
|
||||
|
||||
* thousands separator
|
||||
* decimal separator
|
||||
* decimal precision
|
||||
|
||||
Usage: s := RenderFloat(format, n)
|
||||
|
||||
The format parameter tells how to render the number n.
|
||||
|
||||
http://play.golang.org/p/LXc1Ddm1lJ
|
||||
|
||||
Examples of format strings, given n = 12345.6789:
|
||||
|
||||
"#,###.##" => "12,345.67"
|
||||
"#,###." => "12,345"
|
||||
"#,###" => "12345,678"
|
||||
"#\u202F###,##" => "12 345,67"
|
||||
"#.###,###### => 12.345,678900
|
||||
"" (aka default format) => 12,345.67
|
||||
|
||||
The highest precision allowed is 9 digits after the decimal symbol.
|
||||
There is also a version for integer number, RenderInteger(),
|
||||
which is convenient for calls within template.
|
||||
|
||||
I didn't feel it was worth to publish a library just for this piece
|
||||
of code, hence the snippet. Feel free to reuse as you wish.
|
||||
const rPattern = "#,###.##"
|
||||
*/
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var renderFloatPrecisionMultipliers = [10]float64{
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000,
|
||||
}
|
||||
|
||||
var renderFloatPrecisionRounders = [10]float64{
|
||||
0.5,
|
||||
0.05,
|
||||
0.005,
|
||||
0.0005,
|
||||
0.00005,
|
||||
0.000005,
|
||||
0.0000005,
|
||||
0.00000005,
|
||||
0.000000005,
|
||||
0.0000000005,
|
||||
}
|
||||
|
||||
// func renderInteger(format string, n int) string {
|
||||
// return renderFloat(format, float64(n))
|
||||
// }
|
||||
|
||||
// RenderFloat renders a number to a string
|
||||
func RenderFloat(format string, n float64) string { //nolint:funlen,gocyclo // lots of comments
|
||||
// Special cases:
|
||||
// NaN = "NaN"
|
||||
// +Inf = "+Infinity"
|
||||
// -Inf = "-Infinity"
|
||||
if math.IsNaN(n) {
|
||||
return "NaN"
|
||||
}
|
||||
if n > math.MaxFloat64 {
|
||||
return "Infinity"
|
||||
}
|
||||
if n < -math.MaxFloat64 {
|
||||
return "-Infinity"
|
||||
}
|
||||
|
||||
// default format
|
||||
precision := 2
|
||||
decimalStr := "."
|
||||
thousandStr := ","
|
||||
positiveStr := ""
|
||||
negativeStr := "-"
|
||||
|
||||
if len(format) > 0 {
|
||||
// If there is an explicit format directive,
|
||||
// then default values are these:
|
||||
precision = 9
|
||||
thousandStr = ""
|
||||
|
||||
// collect indices of meaningful formatting directives
|
||||
formatDirectiveChars := []rune(format)
|
||||
formatDirectiveIndices := make([]int, 0)
|
||||
for i, char := range formatDirectiveChars {
|
||||
if char != '#' && char != '0' {
|
||||
formatDirectiveIndices = append(formatDirectiveIndices, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(formatDirectiveIndices) > 0 {
|
||||
// Directive at index 0:
|
||||
// Must be a '+'
|
||||
// Raise an error if not the case
|
||||
// index: 0123456789
|
||||
// +0.000,000
|
||||
// +000,000.0
|
||||
// +0000.00
|
||||
// +0000
|
||||
if formatDirectiveIndices[0] == 0 {
|
||||
if formatDirectiveChars[formatDirectiveIndices[0]] != '+' {
|
||||
panic("RenderFloat(): invalid positive sign directive")
|
||||
}
|
||||
positiveStr = "+"
|
||||
formatDirectiveIndices = formatDirectiveIndices[1:]
|
||||
}
|
||||
|
||||
// Two directives:
|
||||
// First is thousands separator
|
||||
// Raise an error if not followed by 3-digit
|
||||
// 0123456789
|
||||
// 0.000,000
|
||||
// 000,000.00
|
||||
if len(formatDirectiveIndices) == 2 { //nolint:gomnd // not my code
|
||||
if (formatDirectiveIndices[1] - formatDirectiveIndices[0]) != 4 { //nolint:gomnd // not my code
|
||||
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||||
}
|
||||
thousandStr = string(formatDirectiveChars[formatDirectiveIndices[0]])
|
||||
formatDirectiveIndices = formatDirectiveIndices[1:]
|
||||
}
|
||||
|
||||
// One directive:
|
||||
// Directive is decimal separator
|
||||
// The number of digit-specifier following the separator indicates wanted precision
|
||||
// 0123456789
|
||||
// 0.00
|
||||
// 000,0000
|
||||
if len(formatDirectiveIndices) == 1 {
|
||||
decimalStr = string(formatDirectiveChars[formatDirectiveIndices[0]])
|
||||
precision = len(formatDirectiveChars) - formatDirectiveIndices[0] - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate sign part
|
||||
var signStr string
|
||||
if n >= 0.000000001 { //nolint:gomnd,gocritic // not my code!
|
||||
signStr = positiveStr
|
||||
} else if n <= -0.000000001 {
|
||||
signStr = negativeStr
|
||||
n = -n
|
||||
} else {
|
||||
signStr = ""
|
||||
n = 0.0
|
||||
}
|
||||
|
||||
// split number into integer and fractional parts
|
||||
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
|
||||
|
||||
// generate integer part string
|
||||
intStr := strconv.Itoa(int(intf))
|
||||
|
||||
// add thousand separator if required
|
||||
if len(thousandStr) > 0 {
|
||||
for i := len(intStr); i > 3; {
|
||||
i -= 3
|
||||
intStr = intStr[:i] + thousandStr + intStr[i:]
|
||||
}
|
||||
}
|
||||
|
||||
// no fractional part, we can leave now
|
||||
if precision == 0 {
|
||||
return signStr + intStr
|
||||
}
|
||||
|
||||
// generate fractional part
|
||||
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
|
||||
// may need padding
|
||||
if len(fracStr) < precision {
|
||||
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
|
||||
}
|
||||
|
||||
return signStr + intStr + decimalStr + fracStr
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"code.tnxs.net/taxnexus/lib/api/auth0/auth0_client"
|
||||
"code.tnxs.net/taxnexus/lib/api/crm/crm_client"
|
||||
"code.tnxs.net/taxnexus/lib/api/geo/geo_client"
|
||||
"code.tnxs.net/taxnexus/lib/api/stash/stash_client"
|
||||
"code.tnxs.net/taxnexus/lib/app/logger"
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -21,6 +22,7 @@ var appViper = viper.New()
|
|||
var auth0Client = auth0_client.Default
|
||||
var geoClient = geo_client.Default
|
||||
var crmClient = crm_client.Default
|
||||
var stashClient = stash_client.Default
|
||||
var configured = false
|
||||
var apiUsers map[string]User
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"code.tnxs.net/taxnexus/lib/api/stash/stash_client/stash_pdf"
|
||||
"code.tnxs.net/taxnexus/lib/api/stash/stash_models"
|
||||
)
|
||||
|
||||
type StashPDFParams struct {
|
||||
document *Document
|
||||
account *Account
|
||||
objectType string
|
||||
principal *User
|
||||
}
|
||||
|
||||
func StashPDF(params StashPDFParams) (*Document, error) {
|
||||
sugar.Debugf("render.stashPDF: 📥")
|
||||
var title string
|
||||
var fileName string
|
||||
var description string
|
||||
var ref string
|
||||
switch params.objectType {
|
||||
case "account":
|
||||
title = "Taxnexus Report for " + params.account.Name
|
||||
fileName = "Taxnexus Report for " + params.account.Name + ".pdf"
|
||||
description = "Account Report generated by render"
|
||||
ref = "render.getAccounts"
|
||||
case "tax_summary":
|
||||
title = "Taxnexus Tax Report for " + params.account.Name
|
||||
fileName = params.account.Name + " Tax Report.pdf"
|
||||
description = "CDTFA Quarterly District Sales and Use Tax for "
|
||||
ref = "render.getTaxes"
|
||||
}
|
||||
|
||||
todoString := "todo" // todo #5
|
||||
stashParams := stash_pdf.NewPostPdfsParamsWithTimeout(getTimeout)
|
||||
stashParams.PDFRequest = &stash_models.PDFRequest{
|
||||
Meta: &stash_models.RequestMeta{
|
||||
TaxnexusAccount: &todoString,
|
||||
},
|
||||
Data: []*stash_models.NewPDF{
|
||||
{
|
||||
Description: description,
|
||||
Filename: fileName,
|
||||
HTML: params.document.HTML,
|
||||
ObjectType: params.document.SagaType,
|
||||
OwnerID: "fabric", // todo #6 make OwnerID in new PDF real
|
||||
ParentID: params.document.ParentID,
|
||||
Ref: ref,
|
||||
Title: title,
|
||||
},
|
||||
},
|
||||
}
|
||||
response, err := stashClient.StashPdf.PostPdfs(stashParams, params.principal.Auth)
|
||||
if err != nil {
|
||||
sugar.Errorf("render.stashPDF: 💣 ⛔ post PDF to stash error %w", err)
|
||||
return nil, err
|
||||
}
|
||||
var newObj *stash_models.Document
|
||||
for _, itm := range response.Payload.Data { // single iteration execution
|
||||
newObj = itm
|
||||
}
|
||||
sugar.Debugf("render.stashPDF: 👍 📤")
|
||||
return &Document{
|
||||
ID: newObj.ID,
|
||||
Filename: params.account.Name + fileName,
|
||||
SagaType: params.document.SagaType,
|
||||
ParentID: params.document.ParentID,
|
||||
Title: title + params.account.Name,
|
||||
URI: newObj.URI,
|
||||
}, nil
|
||||
}
|
Loading…
Reference in New Issue