-
Notifications
You must be signed in to change notification settings - Fork 37
/
invoice.go
288 lines (242 loc) · 8.99 KB
/
invoice.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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// Copyright (c) 2018, Randy Westlund. All rights reserved.
// This code is under the BSD-2-Clause license.
package quickbooks
import (
"encoding/json"
"errors"
"strconv"
)
// Invoice represents a QuickBooks Invoice object.
type Invoice struct {
Id string `json:"Id,omitempty"`
SyncToken string `json:",omitempty"`
MetaData MetaData `json:",omitempty"`
CustomField []CustomField `json:",omitempty"`
DocNumber string `json:",omitempty"`
TxnDate Date `json:",omitempty"`
DepartmentRef ReferenceType `json:",omitempty"`
PrivateNote string `json:",omitempty"`
LinkedTxn []LinkedTxn `json:"LinkedTxn"`
Line []Line
TxnTaxDetail TxnTaxDetail `json:",omitempty"`
CustomerRef ReferenceType
CustomerMemo MemoRef `json:",omitempty"`
BillAddr PhysicalAddress `json:",omitempty"`
ShipAddr PhysicalAddress `json:",omitempty"`
ClassRef ReferenceType `json:",omitempty"`
SalesTermRef ReferenceType `json:",omitempty"`
DueDate Date `json:",omitempty"`
// GlobalTaxCalculation
ShipMethodRef ReferenceType `json:",omitempty"`
ShipDate Date `json:",omitempty"`
TrackingNum string `json:",omitempty"`
TotalAmt json.Number `json:",omitempty"`
CurrencyRef ReferenceType `json:",omitempty"`
ExchangeRate json.Number `json:",omitempty"`
HomeAmtTotal json.Number `json:",omitempty"`
HomeBalance json.Number `json:",omitempty"`
ApplyTaxAfterDiscount bool `json:",omitempty"`
PrintStatus string `json:",omitempty"`
EmailStatus string `json:",omitempty"`
BillEmail EmailAddress `json:",omitempty"`
BillEmailCC EmailAddress `json:"BillEmailCc,omitempty"`
BillEmailBCC EmailAddress `json:"BillEmailBcc,omitempty"`
DeliveryInfo *DeliveryInfo `json:",omitempty"`
Balance json.Number `json:",omitempty"`
TxnSource string `json:",omitempty"`
AllowOnlineCreditCardPayment bool `json:",omitempty"`
AllowOnlineACHPayment bool `json:",omitempty"`
Deposit json.Number `json:",omitempty"`
DepositToAccountRef ReferenceType `json:",omitempty"`
}
type DeliveryInfo struct {
DeliveryType string
DeliveryTime Date
}
type LinkedTxn struct {
TxnID string `json:"TxnId"`
TxnType string `json:"TxnType"`
}
type TxnTaxDetail struct {
TxnTaxCodeRef ReferenceType `json:",omitempty"`
TotalTax json.Number `json:",omitempty"`
TaxLine []Line `json:",omitempty"`
}
type AccountBasedExpenseLineDetail struct {
AccountRef ReferenceType
TaxAmount json.Number `json:",omitempty"`
// TaxInclusiveAmt json.Number `json:",omitempty"`
// ClassRef ReferenceType `json:",omitempty"`
// TaxCodeRef ReferenceType `json:",omitempty"`
// MarkupInfo MarkupInfo `json:",omitempty"`
// BillableStatus BillableStatusEnum `json:",omitempty"`
// CustomerRef ReferenceType `json:",omitempty"`
}
type Line struct {
Id string `json:",omitempty"`
LineNum int `json:",omitempty"`
Description string `json:",omitempty"`
Amount json.Number
DetailType string
AccountBasedExpenseLineDetail AccountBasedExpenseLineDetail `json:",omitempty"`
SalesItemLineDetail SalesItemLineDetail `json:",omitempty"`
DiscountLineDetail DiscountLineDetail `json:",omitempty"`
TaxLineDetail TaxLineDetail `json:",omitempty"`
}
// TaxLineDetail ...
type TaxLineDetail struct {
PercentBased bool `json:",omitempty"`
NetAmountTaxable json.Number `json:",omitempty"`
// TaxInclusiveAmount json.Number `json:",omitempty"`
// OverrideDeltaAmount
TaxPercent json.Number `json:",omitempty"`
TaxRateRef ReferenceType
}
// SalesItemLineDetail ...
type SalesItemLineDetail struct {
ItemRef ReferenceType `json:",omitempty"`
ClassRef ReferenceType `json:",omitempty"`
UnitPrice json.Number `json:",omitempty"`
// MarkupInfo
Qty float32 `json:",omitempty"`
ItemAccountRef ReferenceType `json:",omitempty"`
TaxCodeRef ReferenceType `json:",omitempty"`
ServiceDate Date `json:",omitempty"`
TaxInclusiveAmt json.Number `json:",omitempty"`
DiscountRate json.Number `json:",omitempty"`
DiscountAmt json.Number `json:",omitempty"`
}
// DiscountLineDetail ...
type DiscountLineDetail struct {
PercentBased bool
DiscountPercent float32 `json:",omitempty"`
}
// CreateInvoice creates the given Invoice on the QuickBooks server, returning
// the resulting Invoice object.
func (c *Client) CreateInvoice(invoice *Invoice) (*Invoice, error) {
var resp struct {
Invoice Invoice
Time Date
}
if err := c.post("invoice", invoice, &resp, nil); err != nil {
return nil, err
}
return &resp.Invoice, nil
}
// DeleteInvoice deletes the invoice
//
// If the invoice was already deleted, QuickBooks returns 400 :(
// The response looks like this:
// {"Fault":{"Error":[{"Message":"Object Not Found","Detail":"Object Not Found : Something you're trying to use has been made inactive. Check the fields with accounts, invoices, items, vendors or employees.","code":"610","element":""}],"type":"ValidationFault"},"time":"2018-03-20T20:15:59.571-07:00"}
//
// This is slightly horrifying and not documented in their API. When this
// happens we just return success; the goal of deleting it has been
// accomplished, just not by us.
func (c *Client) DeleteInvoice(invoice *Invoice) error {
if invoice.Id == "" || invoice.SyncToken == "" {
return errors.New("missing id/sync token")
}
return c.post("invoice", invoice, nil, map[string]string{"operation": "delete"})
}
// FindInvoices gets the full list of Invoices in the QuickBooks account.
func (c *Client) FindInvoices() ([]Invoice, error) {
var resp struct {
QueryResponse struct {
Invoices []Invoice `json:"Invoice"`
MaxResults int
StartPosition int
TotalCount int
}
}
if err := c.query("SELECT COUNT(*) FROM Invoice", &resp); err != nil {
return nil, err
}
if resp.QueryResponse.TotalCount == 0 {
return nil, errors.New("no invoices could be found")
}
invoices := make([]Invoice, 0, resp.QueryResponse.TotalCount)
for i := 0; i < resp.QueryResponse.TotalCount; i += queryPageSize {
query := "SELECT * FROM Invoice ORDERBY Id STARTPOSITION " + strconv.Itoa(i+1) + " MAXRESULTS " + strconv.Itoa(queryPageSize)
if err := c.query(query, &resp); err != nil {
return nil, err
}
if resp.QueryResponse.Invoices == nil {
return nil, errors.New("no invoices could be found")
}
invoices = append(invoices, resp.QueryResponse.Invoices...)
}
return invoices, nil
}
// FindInvoiceById finds the invoice by the given id
func (c *Client) FindInvoiceById(id string) (*Invoice, error) {
var resp struct {
Invoice Invoice
Time Date
}
if err := c.get("invoice/"+id, &resp, nil); err != nil {
return nil, err
}
return &resp.Invoice, nil
}
// QueryInvoices accepts an SQL query and returns all invoices found using it
func (c *Client) QueryInvoices(query string) ([]Invoice, error) {
var resp struct {
QueryResponse struct {
Invoices []Invoice `json:"Invoice"`
StartPosition int
MaxResults int
}
}
if err := c.query(query, &resp); err != nil {
return nil, err
}
if resp.QueryResponse.Invoices == nil {
return nil, errors.New("could not find any invoices")
}
return resp.QueryResponse.Invoices, nil
}
// SendInvoice sends the invoice to the Invoice.BillEmail if emailAddress is left empty
func (c *Client) SendInvoice(invoiceId string, emailAddress string) error {
queryParameters := make(map[string]string)
if emailAddress != "" {
queryParameters["sendTo"] = emailAddress
}
return c.post("invoice/"+invoiceId+"/send", nil, nil, queryParameters)
}
// UpdateInvoice updates the invoice
func (c *Client) UpdateInvoice(invoice *Invoice) (*Invoice, error) {
if invoice.Id == "" {
return nil, errors.New("missing invoice id")
}
existingInvoice, err := c.FindInvoiceById(invoice.Id)
if err != nil {
return nil, err
}
invoice.SyncToken = existingInvoice.SyncToken
payload := struct {
*Invoice
Sparse bool `json:"sparse"`
}{
Invoice: invoice,
Sparse: true,
}
var invoiceData struct {
Invoice Invoice
Time Date
}
if err = c.post("invoice", payload, &invoiceData, nil); err != nil {
return nil, err
}
return &invoiceData.Invoice, err
}
func (c *Client) VoidInvoice(invoice Invoice) error {
if invoice.Id == "" {
return errors.New("missing invoice id")
}
existingInvoice, err := c.FindInvoiceById(invoice.Id)
if err != nil {
return err
}
invoice.SyncToken = existingInvoice.SyncToken
return c.post("invoice", invoice, nil, map[string]string{"operation": "void"})
}