@things-factory/integration-accounting 8.0.0-beta.9 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -14
- package/server/controllers/accounting-api/decorators.ts +47 -0
- package/server/controllers/accounting-api/index.ts +72 -0
- package/server/controllers/index.ts +3 -0
- package/server/controllers/xero/apis/contact/get-contact.ts +38 -0
- package/server/controllers/xero/apis/contact/get-contacts.ts +16 -0
- package/server/controllers/xero/apis/contact/index.ts +2 -0
- package/server/controllers/xero/apis/index.ts +4 -0
- package/server/controllers/xero/apis/invoice/create-invoice.ts +12 -0
- package/server/controllers/xero/apis/invoice/get-invoice.ts +14 -0
- package/server/controllers/xero/apis/invoice/get-invoices.ts +76 -0
- package/server/controllers/xero/apis/invoice/index.ts +4 -0
- package/server/controllers/xero/apis/invoice/update-invoice.ts +12 -0
- package/server/controllers/xero/apis/item/create-item.ts +37 -0
- package/server/controllers/xero/apis/item/delete-item.ts +19 -0
- package/server/controllers/xero/apis/item/get-item.ts +41 -0
- package/server/controllers/xero/apis/item/get-items.ts +41 -0
- package/server/controllers/xero/apis/item/index.ts +6 -0
- package/server/controllers/xero/apis/item/update-item.ts +35 -0
- package/server/controllers/xero/apis/item/update-items.ts +43 -0
- package/server/controllers/xero/apis/purchase-order/get-purchase-orders.ts +61 -0
- package/server/controllers/xero/apis/purchase-order/index.ts +1 -0
- package/server/controllers/xero/index.ts +8 -0
- package/server/controllers/xero/platform-action.ts +53 -0
- package/server/controllers/xero/xero.ts +215 -0
- package/server/engine/connector/accounting-connector.ts +33 -0
- package/server/engine/connector/index.ts +1 -0
- package/server/engine/index.ts +2 -0
- package/server/engine/task/accounting-api.ts +64 -0
- package/server/engine/task/index.ts +1 -0
- package/server/entities/account.ts +86 -0
- package/server/entities/index.ts +5 -0
- package/server/graphql/index.ts +7 -0
- package/server/graphql/resolvers/accounting/account.ts +14 -0
- package/server/graphql/resolvers/accounting/accounts.ts +15 -0
- package/server/graphql/resolvers/accounting/create-account.ts +18 -0
- package/server/graphql/resolvers/accounting/delete-account.ts +20 -0
- package/server/graphql/resolvers/accounting/delete-accounts.ts +25 -0
- package/server/graphql/resolvers/accounting/index.ts +25 -0
- package/server/graphql/resolvers/accounting/update-account.ts +18 -0
- package/server/graphql/resolvers/accounting/update-multiple-accounts.ts +44 -0
- package/server/graphql/resolvers/accounting/xero/deactivate-xero-account.ts +57 -0
- package/server/graphql/resolvers/accounting/xero/get-xero-auth-url.ts +32 -0
- package/server/graphql/resolvers/accounting/xero/index.ts +13 -0
- package/server/graphql/resolvers/accounting/xero/refresh-xero-access-token.ts +67 -0
- package/server/graphql/resolvers/accounting-api/accounting-invoice.ts +36 -0
- package/server/graphql/resolvers/accounting-api/accounting-item.ts +42 -0
- package/server/graphql/resolvers/accounting-api/accounting-purchase-order.ts +14 -0
- package/server/graphql/resolvers/accounting-api/index.ts +14 -0
- package/server/graphql/resolvers/index.ts +5 -0
- package/server/graphql/types/accounting/account-list.ts +8 -0
- package/server/graphql/types/accounting/account-patch.ts +14 -0
- package/server/graphql/types/accounting/account.ts +25 -0
- package/server/graphql/types/accounting/index.ts +44 -0
- package/server/graphql/types/accounting/new-account.ts +12 -0
- package/server/graphql/types/accounting-api/invoice.ts +65 -0
- package/server/graphql/types/accounting-api/item.ts +64 -0
- package/server/graphql/types/accounting-api/purchase-order.ts +30 -0
- package/server/graphql/types/index.ts +10 -0
- package/server/index.ts +9 -0
- package/server/migrations/index.ts +9 -0
- package/server/routers/xero-private-router.ts +24 -0
- package/server/routers/xero-router.ts +154 -0
- package/server/routes.ts +10 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { config, logger } from '@things-factory/env'
|
|
2
|
+
|
|
3
|
+
import { Xero } from './xero'
|
|
4
|
+
|
|
5
|
+
const xeroConfig = config.get('accountingIntegrationXero', {})
|
|
6
|
+
const { apiKey, apiSecret, callback } = xeroConfig
|
|
7
|
+
|
|
8
|
+
function substitute(path, obj) {
|
|
9
|
+
var props = []
|
|
10
|
+
var re = /{([^}]+)}/g
|
|
11
|
+
var text
|
|
12
|
+
|
|
13
|
+
while ((text = re.exec(path))) {
|
|
14
|
+
props.push(text[1])
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
var result = path
|
|
18
|
+
props.forEach(prop => {
|
|
19
|
+
let value = obj[prop.trim()]
|
|
20
|
+
result = result.replace(`{${prop}}`, value === undefined ? '' : value)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return result
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function _action({ accounting, method = 'get', path, request }) {
|
|
27
|
+
const client = new Xero({
|
|
28
|
+
apiKey,
|
|
29
|
+
apiSecret,
|
|
30
|
+
accessToken: accounting.accessToken,
|
|
31
|
+
tenantId: accounting.accountId,
|
|
32
|
+
callback
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const { resource = {}, payload = {} } = request
|
|
36
|
+
|
|
37
|
+
path = substitute(path, resource)
|
|
38
|
+
|
|
39
|
+
return await client[method](path, payload)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const action = async ({ accounting, method = 'get', path, request }) => {
|
|
43
|
+
try {
|
|
44
|
+
return await _action({ accounting, method, path, request })
|
|
45
|
+
} catch (ex) {
|
|
46
|
+
if (ex.status === 401) {
|
|
47
|
+
var refreshedAccounting = await Xero.refreshAccessToken(apiKey, apiSecret, accounting)
|
|
48
|
+
return await _action({ accounting: refreshedAccounting, method, path, request })
|
|
49
|
+
} else {
|
|
50
|
+
logger.error(`Xero: action: ${ex}`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import fetch from 'node-fetch'
|
|
2
|
+
|
|
3
|
+
import { getRepository } from '@things-factory/shell'
|
|
4
|
+
import { parseJwt } from '@things-factory/utils'
|
|
5
|
+
|
|
6
|
+
import { Account } from '../../entities/account'
|
|
7
|
+
|
|
8
|
+
const ENDPOINT = 'https://api.xero.com/api.xro/2.0'
|
|
9
|
+
const debug = require('debug')('things-factory:integration-accounting:xero')
|
|
10
|
+
|
|
11
|
+
export type XeroConfig = {
|
|
12
|
+
apiKey: string
|
|
13
|
+
apiSecret: string
|
|
14
|
+
accessToken?: string
|
|
15
|
+
tenantId?: string
|
|
16
|
+
callback?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class Xero {
|
|
20
|
+
private config: XeroConfig
|
|
21
|
+
|
|
22
|
+
constructor(config: XeroConfig) {
|
|
23
|
+
this.config = {
|
|
24
|
+
...config
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
buildAuthURL(nonce) {
|
|
29
|
+
const scopes = 'offline_access openid profile email accounting.transactions accounting.settings accounting.contacts'
|
|
30
|
+
const { apiKey, callback: redirectUrl } = this.config
|
|
31
|
+
|
|
32
|
+
return `https://login.xero.com/identity/connect/authorize?response_type=code&client_id=${apiKey}&scope=${scopes}&redirect_uri=${redirectUrl}&state=${nonce}`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async get(path: string, data: any) {
|
|
36
|
+
const { accessToken, tenantId } = this.config
|
|
37
|
+
|
|
38
|
+
const qs = Object.entries(data)
|
|
39
|
+
.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`)
|
|
40
|
+
.join('&')
|
|
41
|
+
|
|
42
|
+
const endpoint = `${ENDPOINT}${path}${qs ? '?' + qs : ''}`
|
|
43
|
+
debug('endpoint', endpoint)
|
|
44
|
+
|
|
45
|
+
const response = await fetch(endpoint, {
|
|
46
|
+
method: 'get',
|
|
47
|
+
headers: {
|
|
48
|
+
accept: 'application/json',
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
Authorization: `Bearer ${accessToken}`,
|
|
51
|
+
'xero-tenant-id': tenantId
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw response
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let result = await response.json()
|
|
60
|
+
debug('response result', result)
|
|
61
|
+
result = this.convertStatusCode(result)
|
|
62
|
+
|
|
63
|
+
return result
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async post(path: string, data: any = {}) {
|
|
67
|
+
const { accessToken, tenantId } = this.config
|
|
68
|
+
|
|
69
|
+
debug('data', data)
|
|
70
|
+
|
|
71
|
+
const jsondata = JSON.stringify(data)
|
|
72
|
+
|
|
73
|
+
const endpoint = `${ENDPOINT}${path}`
|
|
74
|
+
debug('endpoint', endpoint)
|
|
75
|
+
|
|
76
|
+
const response = await fetch(endpoint, {
|
|
77
|
+
method: 'post',
|
|
78
|
+
headers: {
|
|
79
|
+
accept: 'application/json',
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
Authorization: `Bearer ${accessToken}`,
|
|
82
|
+
'xero-tenant-id': tenantId
|
|
83
|
+
},
|
|
84
|
+
body: jsondata
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw response
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let result = await response.json()
|
|
92
|
+
debug('response result', result)
|
|
93
|
+
result = this.convertStatusCode(result)
|
|
94
|
+
|
|
95
|
+
return result
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async put(path: string, data: any = {}) {
|
|
99
|
+
const { accessToken, tenantId } = this.config
|
|
100
|
+
|
|
101
|
+
debug('data', data)
|
|
102
|
+
|
|
103
|
+
const jsondata = JSON.stringify(data)
|
|
104
|
+
|
|
105
|
+
const endpoint = `${ENDPOINT}${path}`
|
|
106
|
+
debug('endpoint', endpoint)
|
|
107
|
+
|
|
108
|
+
const response = await fetch(endpoint, {
|
|
109
|
+
method: 'put',
|
|
110
|
+
headers: {
|
|
111
|
+
accept: 'application/json',
|
|
112
|
+
'Content-Type': 'application/json',
|
|
113
|
+
Authorization: `Bearer ${accessToken}`,
|
|
114
|
+
'xero-tenant-id': tenantId
|
|
115
|
+
},
|
|
116
|
+
body: jsondata
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
throw response
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let result = await response.json()
|
|
124
|
+
debug('response result', result)
|
|
125
|
+
result = this.convertStatusCode(result)
|
|
126
|
+
|
|
127
|
+
return result
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async delete(path: string, data: any = {}) {
|
|
131
|
+
const { accessToken, tenantId } = this.config
|
|
132
|
+
|
|
133
|
+
debug('data', data)
|
|
134
|
+
|
|
135
|
+
const jsondata = JSON.stringify(data)
|
|
136
|
+
|
|
137
|
+
const endpoint = `${ENDPOINT}${path}`
|
|
138
|
+
debug('endpoint', endpoint)
|
|
139
|
+
|
|
140
|
+
const response = await fetch(endpoint, {
|
|
141
|
+
method: 'delete',
|
|
142
|
+
headers: {
|
|
143
|
+
accept: 'application/json',
|
|
144
|
+
'Content-Type': 'application/json',
|
|
145
|
+
Authorization: `Bearer ${accessToken}`,
|
|
146
|
+
'xero-tenant-id': tenantId
|
|
147
|
+
},
|
|
148
|
+
body: jsondata
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
throw response
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let result = await response.json()
|
|
156
|
+
debug('response result', result)
|
|
157
|
+
result = this.convertStatusCode(result)
|
|
158
|
+
|
|
159
|
+
return result
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private convertStatusCode(result: Record<string, any>): Record<string, any> {
|
|
163
|
+
result.ok = result.Status.toLowerCase() === 'ok'
|
|
164
|
+
return result
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public static async refreshAccessToken(apiKey, apiSecret, account) {
|
|
168
|
+
const refreshRequestData = {
|
|
169
|
+
grant_type: 'refresh_token',
|
|
170
|
+
refresh_token: account.refreshToken
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const refreshResponse = await fetch(`https://identity.xero.com/connect/token`, {
|
|
174
|
+
method: 'post',
|
|
175
|
+
headers: {
|
|
176
|
+
Authorization: `Basic ${Buffer.from(apiKey + ':' + apiSecret).toString('base64')}`,
|
|
177
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
178
|
+
},
|
|
179
|
+
body: Object.entries(refreshRequestData)
|
|
180
|
+
.map(([key, value]) => encodeURIComponent(key) + '=' + encodeURIComponent(value))
|
|
181
|
+
.join('&')
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
if (!refreshResponse.ok) {
|
|
185
|
+
throw new Error(`get account information failed: ${await refreshResponse.text()}`)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const body = await refreshResponse.json()
|
|
189
|
+
const {
|
|
190
|
+
access_token /* token used to call the API */,
|
|
191
|
+
id_token /* token containing user identity details (only returned if OpenID Connect scopes are requested) */,
|
|
192
|
+
expires_in /* amount of seconds until the access token expires */,
|
|
193
|
+
token_type: tokenType /* must be Bearer */,
|
|
194
|
+
refresh_token
|
|
195
|
+
/* token used to refresh the access token once it has expired (only returned if the offline_access scope is requested).
|
|
196
|
+
*/
|
|
197
|
+
} = body
|
|
198
|
+
|
|
199
|
+
const { exp } = parseJwt(access_token)
|
|
200
|
+
|
|
201
|
+
var patch = {
|
|
202
|
+
accessToken: access_token,
|
|
203
|
+
refreshToken: refresh_token,
|
|
204
|
+
tokenType,
|
|
205
|
+
expiresIn: new Date(exp * 1000)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const repository = getRepository(Account)
|
|
209
|
+
|
|
210
|
+
return await repository.save({
|
|
211
|
+
...account,
|
|
212
|
+
...patch
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ConnectionManager, Connector } from '@things-factory/integration-base'
|
|
2
|
+
|
|
3
|
+
export class AccountingConnector implements Connector {
|
|
4
|
+
async ready(connectionConfigs) {
|
|
5
|
+
await Promise.all(connectionConfigs.map(this.connect))
|
|
6
|
+
|
|
7
|
+
ConnectionManager.logger.info('accounting-connector connections are ready')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async connect(connection) {
|
|
11
|
+
const { domain, name, endpoint } = connection
|
|
12
|
+
|
|
13
|
+
ConnectionManager.addConnectionInstance(connection, { ...connection })
|
|
14
|
+
|
|
15
|
+
ConnectionManager.logger.info(`accounting-connector connection(${name}:${connection.endpoint}) is connected`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async disconnect(connection) {
|
|
19
|
+
ConnectionManager.removeConnectionInstance(connection)
|
|
20
|
+
|
|
21
|
+
ConnectionManager.logger.info(`accounting-connector connection(${connection.name}) is disconnected`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get parameterSpec() {
|
|
25
|
+
return []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get taskPrefixes() {
|
|
29
|
+
return ['accounting']
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
ConnectionManager.registerConnector('accounting-connector', new AccountingConnector())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './accounting-connector'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ConnectionManager, TaskRegistry } from '@things-factory/integration-base'
|
|
2
|
+
import { getRepository } from '@things-factory/shell'
|
|
3
|
+
import { access } from '@things-factory/utils'
|
|
4
|
+
|
|
5
|
+
import { AccountingAPI as API } from '../../controllers/accounting-api'
|
|
6
|
+
import { Account } from '../../entities'
|
|
7
|
+
|
|
8
|
+
async function AccountingAPI(step, { logger, data, domain }) {
|
|
9
|
+
var {
|
|
10
|
+
connection,
|
|
11
|
+
params: { account: name, api, accessor }
|
|
12
|
+
} = step
|
|
13
|
+
|
|
14
|
+
var client = ConnectionManager.getConnectionInstanceByName(domain, connection) || {}
|
|
15
|
+
if (!client) {
|
|
16
|
+
throw new Error(`no connection : ${connection}`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!api) {
|
|
20
|
+
throw new Error(`no api defined`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const repository = getRepository(Account)
|
|
24
|
+
const account: Account = await repository.findOne({
|
|
25
|
+
where: { domain: { id: domain.id }, name }
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (!account) {
|
|
29
|
+
throw new Error(`no account defined`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var result = await API[api](account, accessor ? access(accessor, data) : {})
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
data: result
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
AccountingAPI.parameterSpec = [
|
|
40
|
+
{
|
|
41
|
+
type: 'entity-selector',
|
|
42
|
+
name: 'account',
|
|
43
|
+
label: 'account',
|
|
44
|
+
property: {
|
|
45
|
+
queryName: 'accounts',
|
|
46
|
+
valueKey: 'name'
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'select',
|
|
51
|
+
name: 'api',
|
|
52
|
+
label: 'api',
|
|
53
|
+
property: {
|
|
54
|
+
options: ['', 'getAccountingInvoices']
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: 'string',
|
|
59
|
+
name: 'accessor',
|
|
60
|
+
label: 'accessor'
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
TaskRegistry.registerTaskHandler('accounting-api', AccountingAPI)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './accounting-api'
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { User } from '@things-factory/auth-base'
|
|
2
|
+
import { Domain } from '@things-factory/shell'
|
|
3
|
+
import { Column, CreateDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
|
|
4
|
+
|
|
5
|
+
@Entity()
|
|
6
|
+
@Index('ix_account_0', (account: Account) => [account.domain, account.name], { unique: true })
|
|
7
|
+
export class Account {
|
|
8
|
+
@PrimaryGeneratedColumn('uuid')
|
|
9
|
+
id: string
|
|
10
|
+
|
|
11
|
+
@ManyToOne(type => Domain)
|
|
12
|
+
domain: Domain
|
|
13
|
+
|
|
14
|
+
@Column()
|
|
15
|
+
platform: string
|
|
16
|
+
|
|
17
|
+
@Column({
|
|
18
|
+
nullable: true
|
|
19
|
+
})
|
|
20
|
+
accountId: string
|
|
21
|
+
|
|
22
|
+
@Column()
|
|
23
|
+
countryCode: string
|
|
24
|
+
|
|
25
|
+
@Column({
|
|
26
|
+
nullable: true
|
|
27
|
+
})
|
|
28
|
+
status: string
|
|
29
|
+
|
|
30
|
+
@Column()
|
|
31
|
+
name: string
|
|
32
|
+
|
|
33
|
+
@Column({
|
|
34
|
+
nullable: true
|
|
35
|
+
})
|
|
36
|
+
accessInfo: string
|
|
37
|
+
|
|
38
|
+
@Column({
|
|
39
|
+
nullable: true
|
|
40
|
+
})
|
|
41
|
+
accessToken: string
|
|
42
|
+
|
|
43
|
+
@Column({ default: false, nullable: true })
|
|
44
|
+
trackedInventory: Boolean
|
|
45
|
+
|
|
46
|
+
@Column({
|
|
47
|
+
nullable: true
|
|
48
|
+
})
|
|
49
|
+
refreshToken: string
|
|
50
|
+
|
|
51
|
+
@Column({
|
|
52
|
+
nullable: true
|
|
53
|
+
})
|
|
54
|
+
expiresIn: Date
|
|
55
|
+
|
|
56
|
+
@Column({
|
|
57
|
+
nullable: true
|
|
58
|
+
})
|
|
59
|
+
tokenType: string
|
|
60
|
+
|
|
61
|
+
@Column({
|
|
62
|
+
nullable: true
|
|
63
|
+
})
|
|
64
|
+
accountInfo: string
|
|
65
|
+
|
|
66
|
+
@Column({
|
|
67
|
+
nullable: true
|
|
68
|
+
})
|
|
69
|
+
description: string
|
|
70
|
+
|
|
71
|
+
@CreateDateColumn()
|
|
72
|
+
createdAt: Date
|
|
73
|
+
|
|
74
|
+
@UpdateDateColumn()
|
|
75
|
+
updatedAt: Date
|
|
76
|
+
|
|
77
|
+
@ManyToOne(type => User, {
|
|
78
|
+
nullable: true
|
|
79
|
+
})
|
|
80
|
+
creator: User
|
|
81
|
+
|
|
82
|
+
@ManyToOne(type => User, {
|
|
83
|
+
nullable: true
|
|
84
|
+
})
|
|
85
|
+
updater: User
|
|
86
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getRepository } from '@things-factory/shell'
|
|
2
|
+
|
|
3
|
+
import { Account } from '../../../entities'
|
|
4
|
+
|
|
5
|
+
export const accountResolver = {
|
|
6
|
+
async account(_: any, { id }, context: ResolverContext) {
|
|
7
|
+
const repository = getRepository(Account)
|
|
8
|
+
|
|
9
|
+
return await getRepository(Account).findOne({
|
|
10
|
+
where: { domain: { id: context.state.domain.id }, id },
|
|
11
|
+
relations: ['domain', 'creator', 'updater']
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { convertListParams, getRepository, ListParam } from '@things-factory/shell'
|
|
2
|
+
|
|
3
|
+
import { Account } from '../../../entities'
|
|
4
|
+
|
|
5
|
+
export const accountsResolver = {
|
|
6
|
+
async accounts(_: any, params: ListParam, context: ResolverContext) {
|
|
7
|
+
const { domain } = context.state
|
|
8
|
+
const convertedParams = convertListParams(params, { domain })
|
|
9
|
+
const [items, total] = await getRepository(Account).findAndCount({
|
|
10
|
+
...convertedParams,
|
|
11
|
+
relations: ['domain', 'creator', 'updater']
|
|
12
|
+
})
|
|
13
|
+
return { items, total }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getRepository } from '@things-factory/shell'
|
|
2
|
+
|
|
3
|
+
import { ACCOUNTING_STATUS } from '../../../controllers'
|
|
4
|
+
import { Account } from '../../../entities'
|
|
5
|
+
|
|
6
|
+
export const createAccount = {
|
|
7
|
+
async createAccount(_: any, { account }, context: ResolverContext) {
|
|
8
|
+
const { domain, user } = context.state
|
|
9
|
+
|
|
10
|
+
return await getRepository(Account).save({
|
|
11
|
+
status: ACCOUNTING_STATUS.INACTIVE,
|
|
12
|
+
...account,
|
|
13
|
+
domain,
|
|
14
|
+
creator: user,
|
|
15
|
+
updater: user
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getRepository } from '@things-factory/shell'
|
|
2
|
+
|
|
3
|
+
import { ACCOUNTING_STATUS } from '../../../controllers'
|
|
4
|
+
import { Account } from '../../../entities'
|
|
5
|
+
|
|
6
|
+
export const deleteAccount = {
|
|
7
|
+
async deleteAccount(_: any, { id }, context: ResolverContext) {
|
|
8
|
+
const foundAccount: Account = await getRepository(Account).findOne({
|
|
9
|
+
where: { domain: { id: context.state.domain.id }, id }
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
await getRepository(Account).save({
|
|
13
|
+
...foundAccount,
|
|
14
|
+
status: ACCOUNTING_STATUS.TERMINATED,
|
|
15
|
+
updater: context.state.user
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
return true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { In } from 'typeorm'
|
|
2
|
+
|
|
3
|
+
import { getRepository } from '@things-factory/shell'
|
|
4
|
+
|
|
5
|
+
import { ACCOUNTING_STATUS } from '../../../controllers'
|
|
6
|
+
import { Account } from '../../../entities'
|
|
7
|
+
|
|
8
|
+
export const deleteAccounts = {
|
|
9
|
+
async deleteAccounts(_: any, { ids }, context: ResolverContext) {
|
|
10
|
+
let foundAccounts: Account[] = await getRepository(Account).find({
|
|
11
|
+
where: { domain: { id: context.state.domain.id }, id: In(ids) }
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const updatedAccount = foundAccounts.map((account: Account) => {
|
|
15
|
+
return {
|
|
16
|
+
...account,
|
|
17
|
+
status: ACCOUNTING_STATUS.TERMINATED,
|
|
18
|
+
updater: context.state.user
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
await getRepository(Account).save(updatedAccount)
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { accountResolver } from './account'
|
|
2
|
+
import { accountsResolver } from './accounts'
|
|
3
|
+
|
|
4
|
+
import { updateMultipleAccount } from './update-multiple-accounts'
|
|
5
|
+
import { updateAccount } from './update-account'
|
|
6
|
+
import { createAccount } from './create-account'
|
|
7
|
+
import { deleteAccount } from './delete-account'
|
|
8
|
+
import { deleteAccounts } from './delete-accounts'
|
|
9
|
+
|
|
10
|
+
import * as Xero from './xero'
|
|
11
|
+
|
|
12
|
+
export const Query = {
|
|
13
|
+
...accountsResolver,
|
|
14
|
+
...accountResolver,
|
|
15
|
+
...Xero.Query
|
|
16
|
+
} as any
|
|
17
|
+
|
|
18
|
+
export const Mutation = {
|
|
19
|
+
...updateAccount,
|
|
20
|
+
...updateMultipleAccount,
|
|
21
|
+
...createAccount,
|
|
22
|
+
...deleteAccount,
|
|
23
|
+
...deleteAccounts,
|
|
24
|
+
...Xero.Mutation
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getRepository } from '@things-factory/shell'
|
|
2
|
+
|
|
3
|
+
import { Account } from '../../../entities'
|
|
4
|
+
|
|
5
|
+
export const updateAccount = {
|
|
6
|
+
async updateAccount(_: any, { name, patch }, context: ResolverContext) {
|
|
7
|
+
const repository = getRepository(Account)
|
|
8
|
+
const account: any = await repository.findOne({
|
|
9
|
+
where: { domain: { id: context.state.domain.id }, name }
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
return await repository.save({
|
|
13
|
+
...account,
|
|
14
|
+
...patch,
|
|
15
|
+
updater: context.state.user
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getRepository } from '@things-factory/shell'
|
|
2
|
+
|
|
3
|
+
import { Account } from '../../../entities'
|
|
4
|
+
|
|
5
|
+
export const updateMultipleAccount = {
|
|
6
|
+
async updateMultipleAccount(_: any, { patches }, context: ResolverContext) {
|
|
7
|
+
let results = []
|
|
8
|
+
const _createRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === '+')
|
|
9
|
+
const _updateRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === 'M')
|
|
10
|
+
const accountRepo = getRepository(Account)
|
|
11
|
+
|
|
12
|
+
if (_createRecords.length > 0) {
|
|
13
|
+
for (let i = 0; i < _createRecords.length; i++) {
|
|
14
|
+
const newRecord = _createRecords[i]
|
|
15
|
+
|
|
16
|
+
const result = await accountRepo.save({
|
|
17
|
+
...newRecord,
|
|
18
|
+
domain: context.state.domain,
|
|
19
|
+
creator: context.state.user,
|
|
20
|
+
updater: context.state.user
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
results.push({ ...result, cuFlag: '+' })
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (_updateRecords.length > 0) {
|
|
28
|
+
for (let i = 0; i < _updateRecords.length; i++) {
|
|
29
|
+
const newRecord = _updateRecords[i]
|
|
30
|
+
const account: Account = await accountRepo.findOneBy({ id: newRecord.id })
|
|
31
|
+
|
|
32
|
+
const result = await accountRepo.save({
|
|
33
|
+
...account,
|
|
34
|
+
...newRecord,
|
|
35
|
+
updater: context.state.user
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
results.push({ ...result, cuFlag: 'M' })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return results
|
|
43
|
+
}
|
|
44
|
+
}
|