Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions app/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
import { Module } from "@nestjs/common";
import { RedisModule } from "@recursyve/nestjs-redis";
import {
QuickBooksAttachablesModule,
QuickBooksCustomersModule,
QuickBooksEstimatesModule,
QuickBooksInvoicesModule,
QuickBooksModule,
QuickBooksScopes
QuickBooksPaymentsModule,
QuickBooksScopes,
QuickBooksStore
} from "../../lib";
import { CustomersController } from "./customers/customers.controller";
import { InvoicesController } from "./invoices/invoices.controller";
import { RedisQuickbooksStore } from "./store/redis.quickbooks.store";

@Module({
imports: [
QuickBooksModule.forRoot({
imports: [RedisModule],
config: {
mode: "sandbox",
serverUri: "http://localhost:3000",
serverUri: "https://06dc-72-10-138-232.ngrok.io",
scopes: [QuickBooksScopes.Accounting],
redirection: {
successUrl: "http://localhost:3000/customer",
errorUrl: "http://localhost:3000/customer"
}
},
store: {
provide: QuickBooksStore,
useClass: RedisQuickbooksStore
}
}),
QuickBooksAttachablesModule,
QuickBooksCustomersModule,
QuickBooksInvoicesModule
QuickBooksEstimatesModule,
QuickBooksInvoicesModule,
QuickBooksPaymentsModule
],
controllers: [CustomersController, InvoicesController]
})
Expand Down
38 changes: 26 additions & 12 deletions app/src/customers/customers.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Controller, Get, Post } from "@nestjs/common";
import { Body, Controller, Get, Post } from "@nestjs/common";
import { lastValueFrom } from "rxjs";
import { Op, QuickBooksCustomersService } from "../../../lib";

@Controller("customer")
Expand All @@ -8,22 +9,35 @@ export class CustomersController {

@Get()
public async getAll() {
return (await this.customersService.withDefaultCompany()).query({
/* MetaData: {
LastUpdatedTime: {
[Op.gt]: "2015-03-01"
}
}*/
}).toPromise().then(x => x.QueryResponse.Customer);
const service = await this.customersService.withDefaultCompany();
const count = await lastValueFrom(service.count({}));
return lastValueFrom(service.query({
/* MetaData: {
LastUpdatedTime: {
[Op.gt]: "2015-03-01"
}
}*/
}, { maxResult: count?.QueryResponse?.totalCount })).then(x => x.QueryResponse.Customer);
}

@Get("name")
public async GetByName() {
const service = await this.customersService.withDefaultCompany();
const response = await lastValueFrom(service.query({
DisplayName: {
[Op.contains]: "'"
}
}));
return response.QueryResponse;
}

@Post()
public async create() {
return (await this.customersService.withDefaultCompany()).create({
DisplayName: "My new customer",
public async create(@Body() dto: any) {
return lastValueFrom((await this.customersService.withDefaultCompany()).create({
DisplayName: dto.name,
PrimaryEmailAddr: {
Address: "julien@recursyve.io"
}
}).toPromise();
}));
}
}
43 changes: 40 additions & 3 deletions app/src/invoices/invoices.controller.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { Controller, Get, Header, Param, Put, Res } from "@nestjs/common";
import { Controller, Get, Header, Param, Patch, Put, Res } from "@nestjs/common";
import * as fs from "fs/promises";
import * as path from "path";
import { lastValueFrom } from "rxjs";
import {
Op,
QuickBooksAttachablesService,
QuickBooksAttachablesUploadResponseModel,
QuickBooksInvoicesService
QuickBooksAttachablesUploadResponseModel, QuickBooksEstimatesService,
QuickBooksInvoicesService, QuickBooksPaymentsService
} from "../../../lib";

@Controller("invoice")
export class InvoicesController {
constructor(
private readonly attachablesService: QuickBooksAttachablesService,
private readonly estimatesService: QuickBooksEstimatesService,
private readonly invoicesService: QuickBooksInvoicesService,
private readonly paymentsService: QuickBooksPaymentsService,
) {}

@Get()
Expand Down Expand Up @@ -52,4 +54,39 @@ export class InvoicesController {
]
}));
}

@Patch(":id/customer")
public async updateCustomer(@Param("id") id: string): Promise<void> {
const estimateService = await this.estimatesService.withDefaultCompany();
const estimate = await estimateService.readById("213").toPromise();
await estimateService.sparseUpdate(estimate.Estimate, {
CustomerRef: {
value: "70",
}
}).toPromise();

const paymentService = await this.paymentsService.withDefaultCompany();
const payment = await paymentService.readById("215").toPromise();
const p = await paymentService.fullUpdate(payment.Payment, {
...payment.Payment,
CustomerRef: {
value: "70",
},
Line: []
}).toPromise();

const service = await this.invoicesService.withDefaultCompany();
const invoice = await service.readById(id).toPromise();
await service.sparseUpdate(invoice.Invoice, {
CustomerRef: {
value: "70"
},
LinkedTxn: [
{
TxnType: "Payment",
TxnId: p.Payment.Id
} as any
]
}).toPromise();
}
}
28 changes: 21 additions & 7 deletions lib/modules/common/base.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { HttpService } from "@nestjs/axios";
import { HttpStatus, Injectable } from "@nestjs/common";
import { AxiosError } from "axios";
import { catchError, Observable, of, throwError } from "rxjs";
import { catchError, Observable, throwError } from "rxjs";
import { map, mergeMap } from "rxjs/operators";
import { QueryUtils } from "../../utils/query.utils";
import { QuickBooksAuthService } from "../auth/services/auth.service";
import { QuickbooksUnauthorizedException, QuickbooksBadRequestException } from "./exceptions";
import { WhereOptions } from "./models";
import { QuickbooksBadRequestException, QuickbooksUnauthorizedException } from "./exceptions";
import { QueryOptions, QueryStatementType, WhereOptions } from "./models";

@Injectable()
export class BaseService<Response, Query, QueryResponse> {
Expand All @@ -30,9 +30,23 @@ export class BaseService<Response, Query, QueryResponse> {
return this.authService.mode === "production" ? this.liveUrl : this.sandboxUrl;
}

public query(condition: WhereOptions<Query>): Observable<QueryResponse> {
public query(condition: WhereOptions<Query>, options?: QueryOptions): Observable<QueryResponse> {
return this.getHttpHeaders().pipe(
mergeMap((headers) => this.http.get<QueryResponse>(this.queryUrl(condition), {
mergeMap((headers) => this.http.get<QueryResponse>(this.queryUrl(QueryStatementType.Select, condition, options), {
headers,
params: {
...this.minorVersion
}
}))
).pipe(
map(x => x.data),
catchError((e) => throwError(() => this.catchError(e)))
);
}

public count(condition: WhereOptions<Query>): Observable<QueryResponse> {
return this.getHttpHeaders().pipe(
mergeMap((headers) => this.http.get<QueryResponse>(this.queryUrl(QueryStatementType.Count, condition), {
headers,
params: {
...this.minorVersion
Expand Down Expand Up @@ -80,8 +94,8 @@ export class BaseService<Response, Query, QueryResponse> {
);
}

protected queryUrl(condition: WhereOptions<any>): string {
return `${this.apiUrl}/v3/company/${this.realm}/query?${QueryUtils.generateQuery(this.resource, condition)}`;
protected queryUrl(statement: QueryStatementType, condition: WhereOptions<any>, options?: QueryOptions): string {
return `${this.apiUrl}/v3/company/${this.realm}/query?${QueryUtils.generateQuery(this.resource, statement, condition, options)}`;
}

protected url(path: string): string {
Expand Down
1 change: 1 addition & 0 deletions lib/modules/common/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./quickbooks.exception";
export * from "./quickbooks-unauthorized.exception";
export * from "./quickbooks-bad-request.exception";
10 changes: 10 additions & 0 deletions lib/modules/common/models/query.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,13 @@ export type WhereAttributeHash<T> = {
};

export type WhereOptions<T> = WhereAttributeHash<T> | AndOperator<T>;

export interface QueryOptions {
startPosition?: number;
maxResult?: number;
}

export enum QueryStatementType {
Select,
Count
}
1 change: 1 addition & 0 deletions lib/modules/common/models/response.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export interface QuickBooksDeleteResponseModel {
export interface QuickBooksQueryResponseModel {
startPosition: number;
maxResults: number;
totalCount: number;
}
2 changes: 1 addition & 1 deletion lib/modules/items/services/items.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class QuickBooksCompanyItemsService extends BaseService<
}

public fullUpdate(id: string, token: string, dto: FullUpdateQuickBooksItemsDto): Observable<QuickBooksItemsResponseModel>;
public fullUpdate(customer: QuickBooksItems, dto: FullUpdateQuickBooksItemsDto): Observable<QuickBooksItemsResponseModel>;
public fullUpdate(item: QuickBooksItems, dto: FullUpdateQuickBooksItemsDto): Observable<QuickBooksItemsResponseModel>;
public fullUpdate(
...args: [string | QuickBooksItems, string | FullUpdateQuickBooksItemsDto, FullUpdateQuickBooksItemsDto?]
): Observable<QuickBooksItemsResponseModel> {
Expand Down
4 changes: 2 additions & 2 deletions lib/modules/store/store.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TokensModel } from "../auth/models/tokens.model";
@Injectable()
export abstract class QuickBooksStore {
public abstract registerCompany(realm: string): Promise<void>;
public abstract getDefaultCompany(): Promise<string>;
public abstract getDefaultCompany(): Promise<string | null>;
public abstract setToken(realm: string, token: TokensModel): Promise<void>;
public abstract getToken(realm: string): Promise<TokensModel>;
public abstract getToken(realm: string): Promise<TokensModel | null>;
}
14 changes: 9 additions & 5 deletions lib/utils/operators.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ export class OperatorsUtils {
case Op.notLike:
return `${attribute} not like ${this.transformValue(value)}`;
case Op.beginsWith:
return `${attribute} like '${value}%'`;
return `${attribute} like '${this.escapeString(value)}%'`;
case Op.endsWith:
return `${attribute} like '%${value}'`;
return `${attribute} like '%${this.escapeString(value)}'`;
case Op.contains:
return `${attribute} like '%${value}%'`;
return `${attribute} like '%${this.escapeString(value)}%'`;
}
}

Expand All @@ -40,12 +40,16 @@ export class OperatorsUtils {
return value.toISOString();
}
if (typeof value === "string") {
return `'${value}'`;
return `'${this.escapeString(value)}'`;
}
if (Array.isArray(value)) {
return value.map(x => typeof x === "string" ? `'${x}'` : x).join(",");
return value.map(x => typeof x === "string" ? `'${this.escapeString(x)}'` : x).join(",");
}

return value;
}

public static escapeString(value: string): string {
return value.replace(/'/g, "\\'");
}
}
Loading