* feat(schema): add auth, people, and financial tables Add users, organizations, teams, groups, and project members tables. Extend customers/vendors with netsuite fields. Add netsuite schema for invoices, bills, payments, and credit memos. Include all migrations, seeds, new UI primitives, and config updates. * feat(auth): add WorkOS authentication system Add login, signup, password reset, email verification, and invitation flows via WorkOS AuthKit. Includes auth middleware, permission helpers, dev mode fallbacks, and auth page components. * feat(people): add people management system Add user, team, group, and organization management with CRUD actions, dashboard pages, invite dialog, user drawer, and role-based filtering. Includes WorkOS invitation integration. * feat(netsuite): add NetSuite integration and financials Add bidirectional NetSuite REST API integration with OAuth 2.0, rate limiting, sync engine, and conflict resolution. Includes invoices, vendor bills, payments, credit memos CRUD, customer/vendor management pages, and financial dashboard with tabbed views. * ci: retrigger build * fix: add mobile-list-card dependency for people-table --------- Co-authored-by: Nicholai <nicholaivogelfilms@gmail.com>
138 lines
3.4 KiB
TypeScript
Executable File
138 lines
3.4 KiB
TypeScript
Executable File
import { getRecordUrl } from "../config"
|
|
import { BaseClient } from "./base-client"
|
|
import type {
|
|
NetSuiteListResponse,
|
|
NetSuiteRecord,
|
|
NetSuiteRequestOptions,
|
|
} from "./types"
|
|
|
|
export class RecordClient {
|
|
private client: BaseClient
|
|
private accountId: string
|
|
|
|
constructor(client: BaseClient, accountId: string) {
|
|
this.client = client
|
|
this.accountId = accountId
|
|
}
|
|
|
|
async get<T extends NetSuiteRecord>(
|
|
recordType: string,
|
|
id: string,
|
|
options?: NetSuiteRequestOptions
|
|
): Promise<T> {
|
|
const url = this.buildUrl(recordType, id, options)
|
|
return this.client.request<T>(url)
|
|
}
|
|
|
|
async list<T extends NetSuiteRecord>(
|
|
recordType: string,
|
|
options?: NetSuiteRequestOptions
|
|
): Promise<NetSuiteListResponse<T>> {
|
|
const url = this.buildUrl(recordType, undefined, options)
|
|
return this.client.request<NetSuiteListResponse<T>>(url)
|
|
}
|
|
|
|
async listAll<T extends NetSuiteRecord>(
|
|
recordType: string,
|
|
options?: NetSuiteRequestOptions
|
|
): Promise<T[]> {
|
|
const all: T[] = []
|
|
let offset = 0
|
|
const limit = options?.limit ?? 1000
|
|
|
|
while (true) {
|
|
const response = await this.list<T>(recordType, {
|
|
...options,
|
|
limit,
|
|
offset,
|
|
})
|
|
all.push(...response.items)
|
|
|
|
if (!response.hasMore) break
|
|
offset += limit
|
|
}
|
|
|
|
return all
|
|
}
|
|
|
|
async create(
|
|
recordType: string,
|
|
data: Record<string, unknown>,
|
|
idempotencyKey?: string
|
|
): Promise<{ id: string }> {
|
|
const url = getRecordUrl(this.accountId, recordType)
|
|
const headers: Record<string, string> = {}
|
|
if (idempotencyKey) {
|
|
headers["X-NetSuite-Idempotency-Key"] = idempotencyKey
|
|
}
|
|
|
|
return this.client.request(url, {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
headers,
|
|
})
|
|
}
|
|
|
|
async update(
|
|
recordType: string,
|
|
id: string,
|
|
data: Record<string, unknown>
|
|
): Promise<void> {
|
|
const url = getRecordUrl(this.accountId, recordType, id)
|
|
await this.client.request(url, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(data),
|
|
})
|
|
}
|
|
|
|
async remove(
|
|
recordType: string,
|
|
id: string
|
|
): Promise<void> {
|
|
const url = getRecordUrl(this.accountId, recordType, id)
|
|
await this.client.request(url, { method: "DELETE" })
|
|
}
|
|
|
|
// netsuite record transformations (e.g. sales order -> invoice)
|
|
async transform(
|
|
sourceType: string,
|
|
sourceId: string,
|
|
targetType: string,
|
|
data?: Record<string, unknown>
|
|
): Promise<{ id: string }> {
|
|
const url = `${getRecordUrl(this.accountId, sourceType, sourceId)}/!transform/${targetType}`
|
|
return this.client.request(url, {
|
|
method: "POST",
|
|
body: data ? JSON.stringify(data) : undefined,
|
|
})
|
|
}
|
|
|
|
private buildUrl(
|
|
recordType: string,
|
|
id?: string,
|
|
options?: NetSuiteRequestOptions
|
|
): string {
|
|
const base = getRecordUrl(this.accountId, recordType, id)
|
|
const params = new URLSearchParams()
|
|
|
|
if (options?.fields?.length) {
|
|
params.set("fields", options.fields.join(","))
|
|
}
|
|
if (options?.query) {
|
|
params.set("q", options.query)
|
|
}
|
|
if (options?.limit) {
|
|
params.set("limit", String(options.limit))
|
|
}
|
|
if (options?.offset) {
|
|
params.set("offset", String(options.offset))
|
|
}
|
|
if (options?.expandSubResources) {
|
|
params.set("expandSubResources", "true")
|
|
}
|
|
|
|
const qs = params.toString()
|
|
return qs ? `${base}?${qs}` : base
|
|
}
|
|
}
|