v0.1.36 v0.1.35
Vernon Keenan 2021-01-27 15:11:19 -08:00
parent 3d22cb0f8d
commit 8c67d3a0c4
9 changed files with 450 additions and 21 deletions

View File

@ -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 {

25
app/contact-cache.go Normal file
View File

@ -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
}

49
app/contact-services.go Normal file
View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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
}

13
app/document.go Normal file
View File

@ -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
}

195
app/render-float.go Normal file
View File

@ -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###,##" => "12345,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
}

View File

@ -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

71
app/stash-services.go Normal file
View File

@ -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
}