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 package app
import ( import (
"encoding/json"
"code.tnxs.net/taxnexus/lib/api/crm/crm_models" "code.tnxs.net/taxnexus/lib/api/crm/crm_models"
"code.tnxs.net/taxnexus/lib/api/ops/ops_models" "code.tnxs.net/taxnexus/lib/api/ops/ops_models"
) )
@ -16,6 +18,21 @@ type Address struct {
Street string 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 // MarshalToCrm converts a first class object to swagger
func (obj *Address) MarshalToCrm() *crm_models.Address { func (obj *Address) MarshalToCrm() *crm_models.Address {
if obj == nil { 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 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 // MarshalToSwagger encodes a first class object to swagger
func (obj *Coordinate) MarshalToSwagger() *geo_models.Coordinate { func (obj *Coordinate) MarshalToSwagger() *geo_models.Coordinate {
@ -33,21 +83,21 @@ func (obj *Coordinate) MarshalToSwagger() *geo_models.Coordinate {
IsDistrict: obj.IsDistrict, IsDistrict: obj.IsDistrict,
Latitude: obj.Latitude, Latitude: obj.Latitude,
Longitude: obj.Longitude, Longitude: obj.Longitude,
Map: obj.Map, // Map: obj.Map,
Name: obj.Name, Name: obj.Name,
Neighborhood: obj.Neighborhood, Neighborhood: obj.Neighborhood,
Place: obj.Place, Place: obj.Place,
PlaceGeocode: obj.Geocode, PlaceGeocode: obj.Geocode,
PlaceID: obj.PlaceID, PlaceID: obj.PlaceID,
PostalCode: obj.PostalCode, PostalCode: obj.PostalCode,
Ref: obj.Ref, Ref: obj.Ref,
State: obj.State, State: obj.State,
StateID: obj.StateID, StateID: obj.StateID,
Status: obj.Status, Status: obj.Status,
Street: obj.Street, Street: obj.Street,
StreetNumber: obj.StreetNumber, StreetNumber: obj.StreetNumber,
StreetView: obj.StreetView, // StreetView: obj.StreetView,
TaxTypes: theTaxTypes, TaxTypes: theTaxTypes,
TaxRate: &taxRate, TaxRate: &taxRate,
} }
} }

View File

@ -2,6 +2,7 @@ package app
import ( import (
"database/sql" "database/sql"
"html/template"
"code.tnxs.net/taxnexus/lib/api/geo/geo_models" "code.tnxs.net/taxnexus/lib/api/geo/geo_models"
) )
@ -40,12 +41,15 @@ type CoordinateActivityWrapper struct {
// Coordinate is a first class object type // Coordinate is a first class object type
type Coordinate struct { type Coordinate struct {
ID string ID string
BusinessTaxTypes []*TaxType
CannabisTypes []*TaxType
Country string Country string
CountryID string CountryID string
County string County string
CountyID string CountyID string
CreatedByID string CreatedByID string
CreatedDate sql.NullTime CreatedDate sql.NullTime
ExciseTaxTypes []*TaxType
Focus string Focus string
FormattedAddress string FormattedAddress string
Geocode string Geocode string
@ -54,21 +58,24 @@ type Coordinate struct {
LastModifiedDate sql.NullTime LastModifiedDate sql.NullTime
Latitude float64 Latitude float64
Longitude float64 Longitude float64
Map template.URL
MerchTaxTypes []*TaxType
Name string Name string
Neighborhood string Neighborhood string
Map []byte
OwnerID string OwnerID string
Place string Place string
PlaceID string PlaceID string
PostalCode string PostalCode string
Ref string Ref string
Situs string
State string State string
StateID string StateID string
Status string Status string
Street string Street string
StreetNumber string StreetNumber string
StreetView []byte StreetView template.URL
TaxRate *TaxRate TaxRate *TaxRate
TaxTypes []*TaxType
TaxTypeIDs []*string 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/auth0/auth0_client"
"code.tnxs.net/taxnexus/lib/api/crm/crm_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/geo/geo_client"
"code.tnxs.net/taxnexus/lib/api/stash/stash_client"
"code.tnxs.net/taxnexus/lib/app/logger" "code.tnxs.net/taxnexus/lib/app/logger"
httptransport "github.com/go-openapi/runtime/client" httptransport "github.com/go-openapi/runtime/client"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -21,6 +22,7 @@ var appViper = viper.New()
var auth0Client = auth0_client.Default var auth0Client = auth0_client.Default
var geoClient = geo_client.Default var geoClient = geo_client.Default
var crmClient = crm_client.Default var crmClient = crm_client.Default
var stashClient = stash_client.Default
var configured = false var configured = false
var apiUsers map[string]User 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
}