forked from XeroAPI/xerogolang
/
item.go
227 lines (177 loc) · 7.71 KB
/
item.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
package accounting
import (
"encoding/json"
"encoding/xml"
"time"
"github.com/sipsynergy/xerogolang"
"github.com/sipsynergy/xerogolang/helpers"
"github.com/markbates/goth"
)
//Item is something that is sold or purchased. It can have inventory tracked or not tracked.
type Item struct {
// User defined item code (max length = 30)
Code string `json:"Code" xml:"Code"`
// The inventory asset account for the item. The account must be of type INVENTORY. The COGSAccountCode in PurchaseDetails is also required to create a tracked item
InventoryAssetAccountCode string `json:"InventoryAssetAccountCode" xml:"InventoryAssetAccountCode"`
// The name of the item (max length = 50)
Name string `json:"Name,omitempty" xml:"Name,omitempty"`
// Boolean value, defaults to true. When IsSold is true the item will be available on sales transactions in the Xero UI. If IsSold is updated to false then Description and SalesDetails values will be nulled.
IsSold bool `json:"IsSold,omitempty" xml:"IsSold,omitempty"`
// Boolean value, defaults to true. When IsPurchased is true the item is available for purchase transactions in the Xero UI. If IsPurchased is updated to false then PurchaseDescription and PurchaseDetails values will be nulled.
IsPurchased bool `json:"IsPurchased,omitempty" xml:"IsPurchased,omitempty"`
// The sales description of the item (max length = 4000)
Description string `json:"Description,omitempty" xml:"Description,omitempty"`
// The purchase description of the item (max length = 4000)
PurchaseDescription string `json:"PurchaseDescription,omitempty" xml:"PurchaseDescription,omitempty"`
// See Purchases & Sales
PurchaseDetails PurchaseAndSaleDetails `json:"PurchaseDetails,omitempty" xml:"PurchaseDetails,omitempty"`
// See Purchases & Sales
SalesDetails PurchaseAndSaleDetails `json:"SalesDetails,omitempty" xml:"SalesDetails,omitempty"`
// True for items that are tracked as inventory. An item will be tracked as inventory if the InventoryAssetAccountCode and COGSAccountCode are set.
IsTrackedAsInventory bool `json:"IsTrackedAsInventory,omitempty" xml:"-"`
// The value of the item on hand. Calculated using average cost accounting.
TotalCostPool float64 `json:"TotalCostPool,omitempty" xml:"-"`
// The quantity of the item on hand
QuantityOnHand float64 `json:"QuantityOnHand,omitempty" xml:"-"`
// Last modified date in UTC format
UpdatedDateUTC string `json:"UpdatedDateUTC,omitempty" xml:"-"`
// The Xero identifier for an Item
ItemID string `json:"ItemID,omitempty" xml:"ItemID,omitempty"`
}
//Items is a collection of Items
type Items struct {
Items []Item `json:"Items" xml:"Item"`
}
//PurchaseAndSaleDetails are Elements for Purchases and Sales
type PurchaseAndSaleDetails struct {
//Unit Price of the item. By default UnitPrice is returned to two decimal places. You can use 4 decimal places by adding the unitdp=4 querystring parameter to your request.
UnitPrice float64 `json:"UnitPrice,omitempty" xml:"UnitPrice,omitempty"`
//Default account code to be used for purchased/sale. Not applicable to the purchase details of tracked items
AccountCode string `json:"AccountCode,omitempty" xml:"AccountCode,omitempty"`
//Cost of goods sold account. Only applicable to the purchase details of tracked items.
COGSAccountCode string `json:"COGSAccountCode,omitempty" xml:"COGSAccountCode,omitempty"`
//Used as an override if the default Tax Code for the selected AccountCode is not correct - see TaxTypes.
TaxType string `json:"TaxType,omitempty" xml:"TaxType,omitempty"`
}
//The Xero API returns Dates based on the .Net JSON date format available at the time of development
//We need to convert these to a more usable format - RFC3339 for consistency with what the API expects to recieve
func (i *Items) convertDates() error {
var err error
for n := len(i.Items) - 1; n >= 0; n-- {
i.Items[n].UpdatedDateUTC, err = helpers.DotNetJSONTimeToRFC3339(i.Items[n].UpdatedDateUTC, true)
if err != nil {
return err
}
}
return nil
}
func unmarshalItem(itemResponseBytes []byte) (*Items, error) {
var itemResponse *Items
err := json.Unmarshal(itemResponseBytes, &itemResponse)
if err != nil {
return nil, err
}
err = itemResponse.convertDates()
if err != nil {
return nil, err
}
return itemResponse, err
}
//Create will create items given an Items struct
func (i *Items) Create(provider *xerogolang.Provider, session goth.Session) (*Items, error) {
additionalHeaders := map[string]string{
"Accept": "application/json",
"Content-Type": "application/xml",
}
body, err := xml.MarshalIndent(i, " ", " ")
if err != nil {
return nil, err
}
itemResponseBytes, err := provider.Create(session, "Items", additionalHeaders, body)
if err != nil {
return nil, err
}
return unmarshalItem(itemResponseBytes)
}
//Update will update an item given an Items struct
//This will only handle single item - you cannot update multiple items in a single call
func (i *Items) Update(provider *xerogolang.Provider, session goth.Session) (*Items, error) {
additionalHeaders := map[string]string{
"Accept": "application/json",
"Content-Type": "application/xml",
}
body, err := xml.MarshalIndent(i, " ", " ")
if err != nil {
return nil, err
}
itemResponseBytes, err := provider.Update(session, "Items/"+i.Items[0].ItemID, additionalHeaders, body)
if err != nil {
return nil, err
}
return unmarshalItem(itemResponseBytes)
}
//FindItemsModifiedSince will get all items modified after a specified date.
//additional querystringParameters such as where, page, order can be added as a map
func FindItemsModifiedSince(provider *xerogolang.Provider, session goth.Session, modifiedSince time.Time, querystringParameters map[string]string) (*Items, error) {
additionalHeaders := map[string]string{
"Accept": "application/json",
}
if !modifiedSince.Equal(dayZero) {
additionalHeaders["If-Modified-Since"] = modifiedSince.Format(time.RFC3339)
}
itemResponseBytes, err := provider.Find(session, "Items", additionalHeaders, querystringParameters)
if err != nil {
return nil, err
}
return unmarshalItem(itemResponseBytes)
}
//FindItems will get all items.
func FindItems(provider *xerogolang.Provider, session goth.Session, querystringParameters map[string]string) (*Items, error) {
return FindItemsModifiedSince(provider, session, dayZero, querystringParameters)
}
//FindItem will get a single item - itemID must be a GUID for an item
func FindItem(provider *xerogolang.Provider, session goth.Session, itemID string) (*Items, error) {
additionalHeaders := map[string]string{
"Accept": "application/json",
}
itemResponseBytes, err := provider.Find(session, "Items/"+itemID, additionalHeaders, nil)
if err != nil {
return nil, err
}
return unmarshalItem(itemResponseBytes)
}
//RemoveItem will get a single item - itemID must be a GUID for an item
func RemoveItem(provider *xerogolang.Provider, session goth.Session, itemID string) (*Items, error) {
additionalHeaders := map[string]string{
"Accept": "application/json",
}
itemResponseBytes, err := provider.Remove(session, "Items/"+itemID, additionalHeaders)
if err != nil {
return nil, err
}
return unmarshalItem(itemResponseBytes)
}
//GenerateExampleItem Creates an Example item
func GenerateExampleItem() *Items {
item := Item{
Code: "42",
Name: "The Executive",
Description: "A Beltless Trenchcoat",
PurchaseDescription: "A Beltless Trenchcoat",
IsSold: true,
IsPurchased: true,
PurchaseDetails: PurchaseAndSaleDetails{
UnitPrice: 140.00,
AccountCode: "300",
},
SalesDetails: PurchaseAndSaleDetails{
UnitPrice: 300.00,
AccountCode: "200",
},
}
itemCollection := &Items{
Items: []Item{},
}
itemCollection.Items = append(itemCollection.Items, item)
return itemCollection
}