/
generator.go
214 lines (186 loc) · 6.19 KB
/
generator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
package ediinvoice
import (
"bytes"
"fmt"
"reflect"
"github.com/go-playground/validator/v10"
"go.uber.org/zap"
"github.com/transcom/mymove/pkg/edi"
edisegment "github.com/transcom/mymove/pkg/edi/segment"
)
// ICNSequenceName used to query Interchange Control Numbers from DB
const ICNSequenceName = "interchange_control_number"
// ICNRandomMin is the smallest allowed random-number based ICN (we use random ICN numbers in development)
const ICNRandomMin int64 = 100000000
// ICNRandomMax is the largest allowed random-number based ICN (we use random ICN numbers in development)
const ICNRandomMax int64 = 999999999
// Invoice858C holds all the segments that are generated
type Invoice858C struct {
ISA edisegment.ISA
GS edisegment.GS
ST edisegment.ST
Header InvoiceHeader
ServiceItems []ServiceItemSegments `validate:"min=1,dive"`
L3 edisegment.L3
SE edisegment.SE
GE edisegment.GE
IEA edisegment.IEA
}
// InvoiceHeader holds all of the segments that are part of an Invoice858C's Header
type InvoiceHeader struct {
ShipmentInformation edisegment.BX
PaymentRequestNumber edisegment.N9
ContractCode edisegment.N9
ServiceMemberName edisegment.N9
OrderPayGrade edisegment.N9
ServiceMemberBranch edisegment.N9
ServiceMemberDodID edisegment.N9
MoveCode edisegment.N9
Currency edisegment.C3
RequestedPickupDate *edisegment.G62
ScheduledPickupDate *edisegment.G62
ActualPickupDate *edisegment.G62
BuyerOrganizationName edisegment.N1
SellerOrganizationName edisegment.N1
DestinationName edisegment.N1
DestinationStreetAddress *edisegment.N3
DestinationPostalDetails edisegment.N4
DestinationPhone *edisegment.PER
OriginName edisegment.N1
OriginStreetAddress *edisegment.N3
OriginPostalDetails edisegment.N4
OriginPhone *edisegment.PER
}
// InvoiceResponseHeader holds all the segments used in the headers of the 997, 824 and 810 response types
type InvoiceResponseHeader struct {
ISA edisegment.ISA
GS edisegment.GS
ST edisegment.ST
}
// ServiceItemSegmentsSizeWithoutFA2s is the number of fields in the ServiceItemSegments struct that does not include the FA2s
const ServiceItemSegmentsSizeWithoutFA2s int = 6
// ServiceItemSegments holds segments that are required for every service item
type ServiceItemSegments struct {
HL edisegment.HL
N9 edisegment.N9
L5 edisegment.L5
L0 edisegment.L0
L1 edisegment.L1
FA1 edisegment.FA1
FA2s []edisegment.FA2
}
// NonEmptySegments produces an array of all of the fields
// in an InvoiceHeader that are not nil
func (ih *InvoiceHeader) NonEmptySegments() []edisegment.Segment {
var result []edisegment.Segment
// This array should contain every field of InvoiceHeader
fields := []edisegment.Segment{
&ih.ShipmentInformation,
&ih.PaymentRequestNumber,
&ih.ContractCode,
&ih.ServiceMemberName,
&ih.OrderPayGrade,
&ih.ServiceMemberBranch,
&ih.ServiceMemberDodID,
&ih.MoveCode,
&ih.Currency,
ih.RequestedPickupDate,
ih.ScheduledPickupDate,
ih.ActualPickupDate,
&ih.BuyerOrganizationName,
&ih.SellerOrganizationName,
&ih.DestinationName,
ih.DestinationStreetAddress,
&ih.DestinationPostalDetails,
ih.DestinationPhone,
&ih.OriginName,
ih.OriginStreetAddress,
&ih.OriginPostalDetails,
ih.OriginPhone,
}
for _, f := range fields {
// An interface value holding a nil pointer is not nil, so we have to use
// reflect here instead of just checking f != nil
if !(reflect.ValueOf(f).Kind() == reflect.Ptr &&
reflect.ValueOf(f).IsNil()) {
result = append(result, f)
}
}
return result
}
// Size returns the number of fields in an InvoiceHeader that are not nil
func (ih *InvoiceHeader) Size() int {
return len(ih.NonEmptySegments())
}
var validate *validator.Validate
func init() {
validate = validator.New()
}
// Segments returns the invoice as an array of rows (string arrays),
// each containing a segment, to prepare it for writing
func (invoice Invoice858C) Segments() [][]string {
records := [][]string{
invoice.ISA.StringArray(),
invoice.GS.StringArray(),
invoice.ST.StringArray(),
}
for _, line := range invoice.Header.NonEmptySegments() {
records = append(records, line.StringArray())
}
for _, line := range invoice.ServiceItems {
records = append(records,
line.HL.StringArray(),
line.N9.StringArray(),
line.L5.StringArray(),
line.L0.StringArray(),
line.L1.StringArray(),
line.FA1.StringArray(),
)
for _, fa2 := range line.FA2s {
records = append(records, fa2.StringArray())
}
}
records = append(records, invoice.L3.StringArray())
records = append(records, invoice.SE.StringArray())
records = append(records, invoice.GE.StringArray())
records = append(records, invoice.IEA.StringArray())
return records
}
func logValidationErrors(logger *zap.Logger, err error) {
// saftey check err is nil just return
if err == nil {
return
}
if _, ok := err.(*validator.InvalidValidationError); ok {
logger.Error("InvalidValidationError", zap.Error(err))
return
}
errs := err.(validator.ValidationErrors)
strErrs := make([]string, len(errs))
for i, err := range errs {
strErrs[i] = fmt.Sprintf("%v (value '%s')", err, err.Value())
}
logger.Error("ValidationErrors", zap.Strings("errors", strErrs))
}
// EDIString returns the EDI representation of an 858C
func (invoice Invoice858C) EDIString(logger *zap.Logger) (string, error) {
err := invoice.Validate()
if err != nil {
// Log validation details, but do not expose details via API
logValidationErrors(logger, err)
return "", fmt.Errorf("EDI failed validation: %w", err)
}
var b bytes.Buffer
ediWriter := edi.NewWriter(&b)
err = ediWriter.WriteAll(invoice.Segments())
if err != nil {
return "", fmt.Errorf("EDI failed write: %w", err)
}
return b.String(), err
}
// Validate will validate the invoice struct (and nested structs) to make sure they will produce legal EDI.
// This returns either an InvalidValidationError or a validator.ValidationErrors that allows all validation
// errors to be introspected individually.
func (invoice Invoice858C) Validate() error {
return validate.Struct(invoice)
}