Merge branch 'develop' into feat/ee-workspaces-multi-org-pr
This commit is contained in:
+3
-1
@@ -23,7 +23,9 @@
|
|||||||
"tenants:migrate:latest": "lerna run cli:tenants:migrate:latest --scope \"@bigcapital/server\"",
|
"tenants:migrate:latest": "lerna run cli:tenants:migrate:latest --scope \"@bigcapital/server\"",
|
||||||
"system:seed:latest": "lerna run cli:system:seed:latest --scope \"@bigcapital/server\"",
|
"system:seed:latest": "lerna run cli:system:seed:latest --scope \"@bigcapital/server\"",
|
||||||
"tenants:seed:latest": "lerna run cli:tenants:seed:latest --scope \"@bigcapital/server\"",
|
"tenants:seed:latest": "lerna run cli:tenants:seed:latest --scope \"@bigcapital/server\"",
|
||||||
"generate:sdk-types": "lerna run openapi:export --scope \"@bigcapital/server\" && lerna run generate --scope \"@bigcapital/sdk-ts\" && lerna run build --scope \"@bigcapital/sdk-ts\""
|
"generate:sdk-types": "lerna run openapi:export --scope \"@bigcapital/server\" && lerna run generate --scope \"@bigcapital/sdk-ts\" && lerna run build --scope \"@bigcapital/sdk-ts\"",
|
||||||
|
"format": "lerna run format",
|
||||||
|
"format:check": "lerna run format:check"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.4.2",
|
"@commitlint/cli": "^17.4.2",
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
exports.up = function(knex) {
|
||||||
|
return knex.schema.alterTable('contacts', table => {
|
||||||
|
table.string('code').nullable().unique();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex) {
|
||||||
|
return knex.schema.alterTable('contacts', table => {
|
||||||
|
table.dropColumn('code');
|
||||||
|
});
|
||||||
|
};
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.alterTable('documents', (table) => {
|
||||||
|
table.unique('key');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.alterTable('documents', (table) => {
|
||||||
|
table.dropUnique('key');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"non_current_assets": "Non-Current Assets",
|
"non_current_assets": "Non-Current Assets",
|
||||||
"liabilities_and_equity": "Liabilities and Equity",
|
"liabilities_and_equity": "Liabilities and Equity",
|
||||||
"liabilities": "Liabilities",
|
"liabilities": "Liabilities",
|
||||||
"current_liabilties": "Current Liabilties",
|
"current_liabilities": "Current Liabilities",
|
||||||
"long_term_liabilities": "Long-Term Liabilities",
|
"long_term_liabilities": "Long-Term Liabilities",
|
||||||
"non_current_liabilities": "Non-Current Liabilities",
|
"non_current_liabilities": "Non-Current Liabilities",
|
||||||
"equity": "Equity",
|
"equity": "Equity",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { createBullBoardAuthMiddleware } from '@/middleware/bull-board-auth.midd
|
|||||||
import { BullModule } from '@nestjs/bullmq';
|
import { BullModule } from '@nestjs/bullmq';
|
||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
import { PassportModule } from '@nestjs/passport';
|
import { PassportModule } from '@nestjs/passport';
|
||||||
import { ClsModule, ClsService } from 'nestjs-cls';
|
import { ClsModule } from 'nestjs-cls';
|
||||||
import { AppController } from './App.controller';
|
import { AppController } from './App.controller';
|
||||||
import { AppService } from './App.service';
|
import { AppService } from './App.service';
|
||||||
import { ItemsModule } from '../Items/Items.module';
|
import { ItemsModule } from '../Items/Items.module';
|
||||||
@@ -170,9 +170,6 @@ import { AppThrottleModule } from './AppThrottle.module';
|
|||||||
global: true,
|
global: true,
|
||||||
middleware: {
|
middleware: {
|
||||||
mount: true,
|
mount: true,
|
||||||
setup: (cls: ClsService, req: Request, res: Response) => {
|
|
||||||
cls.set('organizationId', req.headers['organization-id']);
|
|
||||||
},
|
|
||||||
generateId: true,
|
generateId: true,
|
||||||
saveReq: true,
|
saveReq: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
import * as multerS3 from 'multer-s3';
|
import * as multerS3 from 'multer-s3';
|
||||||
|
import { ClsService } from 'nestjs-cls';
|
||||||
import { S3_CLIENT, S3Module } from "../S3/S3.module";
|
import { S3_CLIENT, S3Module } from "../S3/S3.module";
|
||||||
import { DeleteAttachment } from "./DeleteAttachment";
|
import { DeleteAttachment } from "./DeleteAttachment";
|
||||||
import { GetAttachment } from "./GetAttachment";
|
import { GetAttachment } from "./GetAttachment";
|
||||||
@@ -59,8 +61,12 @@ const models = [
|
|||||||
AttachmentUploadPipeline,
|
AttachmentUploadPipeline,
|
||||||
{
|
{
|
||||||
provide: MULTER_MODULE_OPTIONS,
|
provide: MULTER_MODULE_OPTIONS,
|
||||||
inject: [ConfigService, S3_CLIENT],
|
inject: [ConfigService, S3_CLIENT, ClsService],
|
||||||
useFactory: (configService: ConfigService, s3: S3Client) => ({
|
useFactory: (
|
||||||
|
configService: ConfigService,
|
||||||
|
s3: S3Client,
|
||||||
|
cls: ClsService,
|
||||||
|
) => ({
|
||||||
storage: multerS3({
|
storage: multerS3({
|
||||||
s3,
|
s3,
|
||||||
bucket: configService.get('s3.bucket'),
|
bucket: configService.get('s3.bucket'),
|
||||||
@@ -69,7 +75,11 @@ const models = [
|
|||||||
cb(null, { fieldName: file.fieldname });
|
cb(null, { fieldName: file.fieldname });
|
||||||
},
|
},
|
||||||
key: function (req, file, cb) {
|
key: function (req, file, cb) {
|
||||||
cb(null, Date.now().toString());
|
const organizationId = cls.get<string>('organizationId');
|
||||||
|
if (!organizationId) {
|
||||||
|
return cb(new Error('Tenant context required for upload.'), undefined as any);
|
||||||
|
}
|
||||||
|
cb(null, `${organizationId}/${randomUUID()}`);
|
||||||
},
|
},
|
||||||
acl: function(req, file, cb) {
|
acl: function(req, file, cb) {
|
||||||
// Conditionally set file to public or private based on isPublic flag
|
// Conditionally set file to public or private based on isPublic flag
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ import { AttachmentUploadPipeline } from './S3UploadPipeline';
|
|||||||
import { FileInterceptor } from '@/common/interceptors/file.interceptor';
|
import { FileInterceptor } from '@/common/interceptors/file.interceptor';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
||||||
|
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
|
||||||
|
import { AbilitySubject } from '@/modules/Roles/Roles.types';
|
||||||
|
import { AttachmentAction } from './Attachments.types';
|
||||||
|
|
||||||
@ApiTags('Attachments')
|
@ApiTags('Attachments')
|
||||||
@Controller('/attachments')
|
@Controller('/attachments')
|
||||||
@@ -86,6 +89,7 @@ export class AttachmentsController {
|
|||||||
@ApiOperation({ summary: 'Get attachment by ID' })
|
@ApiOperation({ summary: 'Get attachment by ID' })
|
||||||
@ApiParam({ name: 'id', description: 'Attachment ID' })
|
@ApiParam({ name: 'id', description: 'Attachment ID' })
|
||||||
@ApiResponse({ status: 200, description: 'Returns the attachment file' })
|
@ApiResponse({ status: 200, description: 'Returns the attachment file' })
|
||||||
|
@RequirePermission(AttachmentAction.View, AbilitySubject.Attachment)
|
||||||
async getAttachment(
|
async getAttachment(
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
@Param('id') documentId: string,
|
@Param('id') documentId: string,
|
||||||
@@ -93,11 +97,12 @@ export class AttachmentsController {
|
|||||||
const data = await this.attachmentsApplication.get(documentId);
|
const data = await this.attachmentsApplication.get(documentId);
|
||||||
|
|
||||||
const byte = await data.Body.transformToByteArray();
|
const byte = await data.Body.transformToByteArray();
|
||||||
const extension = mime.extension(data.ContentType);
|
const contentType = data.ContentType || 'application/octet-stream';
|
||||||
|
const extension = mime.extension(contentType) || 'bin';
|
||||||
const buffer = Buffer.from(byte);
|
const buffer = Buffer.from(byte);
|
||||||
|
|
||||||
res.set('Content-Disposition', `filename="${documentId}.${extension}"`);
|
res.set('Content-Disposition', `filename="${documentId}.${extension}"`);
|
||||||
res.set('Content-Type', data.ContentType);
|
res.set('Content-Type', contentType);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +116,7 @@ export class AttachmentsController {
|
|||||||
status: 200,
|
status: 200,
|
||||||
description: 'The document has been deleted successfully',
|
description: 'The document has been deleted successfully',
|
||||||
})
|
})
|
||||||
|
@RequirePermission(AttachmentAction.Delete, AbilitySubject.Attachment)
|
||||||
async deleteAttachment(@Param('id') documentId: string) {
|
async deleteAttachment(@Param('id') documentId: string) {
|
||||||
await this.attachmentsApplication.delete(documentId);
|
await this.attachmentsApplication.delete(documentId);
|
||||||
|
|
||||||
@@ -184,6 +190,7 @@ export class AttachmentsController {
|
|||||||
status: 200,
|
status: 200,
|
||||||
description: 'Returns the presigned URL for the attachment',
|
description: 'Returns the presigned URL for the attachment',
|
||||||
})
|
})
|
||||||
|
@RequirePermission(AttachmentAction.View, AbilitySubject.Attachment)
|
||||||
async getAttachmentPresignedUrl(@Param('id') documentKey: string) {
|
async getAttachmentPresignedUrl(@Param('id') documentKey: string) {
|
||||||
const presignedUrl =
|
const presignedUrl =
|
||||||
await this.attachmentsApplication.getPresignedUrl(documentKey);
|
await this.attachmentsApplication.getPresignedUrl(documentKey);
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
export interface AttachmentLinkDTO {
|
export interface AttachmentLinkDTO {
|
||||||
key: string;
|
key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum AttachmentAction {
|
||||||
|
View = 'View',
|
||||||
|
Delete = 'Delete',
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,17 +31,17 @@ export class DeleteAttachment {
|
|||||||
* @param {string} filekey
|
* @param {string} filekey
|
||||||
*/
|
*/
|
||||||
async delete(filekey: string): Promise<void> {
|
async delete(filekey: string): Promise<void> {
|
||||||
|
const foundDocument = await this.documentModel()
|
||||||
|
.query()
|
||||||
|
.findOne('key', filekey)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
Bucket: this.configService.get('s3.bucket'),
|
Bucket: this.configService.get('s3.bucket'),
|
||||||
Key: filekey,
|
Key: filekey,
|
||||||
};
|
};
|
||||||
await this.s3Client.send(new DeleteObjectCommand(params));
|
await this.s3Client.send(new DeleteObjectCommand(params));
|
||||||
|
|
||||||
const foundDocument = await this.documentModel()
|
|
||||||
.query()
|
|
||||||
.findOne('key', filekey)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
await this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
await this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||||
// Delete all document links
|
// Delete all document links
|
||||||
await this.documentLinkModel()
|
await this.documentLinkModel()
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { S3_CLIENT } from '../S3/S3.module';
|
import { S3_CLIENT } from '../S3/S3.module';
|
||||||
|
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||||
|
import { DocumentModel } from './models/Document.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetAttachment {
|
export class GetAttachment {
|
||||||
@@ -10,13 +12,21 @@ export class GetAttachment {
|
|||||||
|
|
||||||
@Inject(S3_CLIENT)
|
@Inject(S3_CLIENT)
|
||||||
private readonly s3: S3Client,
|
private readonly s3: S3Client,
|
||||||
|
|
||||||
|
@Inject(DocumentModel.name)
|
||||||
|
private readonly documentModel: TenantModelProxy<typeof DocumentModel>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves data of the given document key.
|
* Retrieves data of the given document key.
|
||||||
* @param {string} filekey
|
* @param {string} filekey
|
||||||
*/
|
*/
|
||||||
async getAttachment(filekey: string) {
|
async getAttachment(filekey: string) {
|
||||||
|
await this.documentModel()
|
||||||
|
.query()
|
||||||
|
.findOne('key', filekey)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
Bucket: this.configService.get('s3.bucket'),
|
Bucket: this.configService.get('s3.bucket'),
|
||||||
Key: filekey,
|
Key: filekey,
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ export class GetAttachmentPresignedUrl {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
async getPresignedUrl(key: string) {
|
async getPresignedUrl(key: string) {
|
||||||
const foundDocument = await this.documentModel().query().findOne({ key });
|
const foundDocument = await this.documentModel()
|
||||||
|
.query()
|
||||||
|
.findOne({ key })
|
||||||
|
.throwIfNotFound();
|
||||||
const config = this.configService.get('s3');
|
const config = this.configService.get('s3');
|
||||||
|
|
||||||
let ResponseContentDisposition = 'attachment';
|
let ResponseContentDisposition = 'attachment';
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ClsService } from 'nestjs-cls';
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { SystemUser } from '@/modules/System/models/SystemUser';
|
import { SystemUser } from '@/modules/System/models/SystemUser';
|
||||||
|
import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||||
import { ModelObject } from 'objection';
|
import { ModelObject } from 'objection';
|
||||||
import { JwtPayload } from '../Auth.interfaces';
|
import { JwtPayload } from '../Auth.interfaces';
|
||||||
import { InvalidEmailPasswordException } from '../exceptions/InvalidEmailPassword.exception';
|
import { InvalidEmailPasswordException } from '../exceptions/InvalidEmailPassword.exception';
|
||||||
@@ -12,6 +13,10 @@ export class AuthSigninService {
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(SystemUser.name)
|
@Inject(SystemUser.name)
|
||||||
private readonly systemUserModel: typeof SystemUser,
|
private readonly systemUserModel: typeof SystemUser,
|
||||||
|
|
||||||
|
@Inject(TenantModel.name)
|
||||||
|
private readonly tenantModel: typeof TenantModel,
|
||||||
|
|
||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
private readonly clsService: ClsService,
|
private readonly clsService: ClsService,
|
||||||
) { }
|
) { }
|
||||||
@@ -49,6 +54,7 @@ export class AuthSigninService {
|
|||||||
*/
|
*/
|
||||||
async verifyPayload(payload: JwtPayload): Promise<any> {
|
async verifyPayload(payload: JwtPayload): Promise<any> {
|
||||||
let user: SystemUser;
|
let user: SystemUser;
|
||||||
|
let tenant: TenantModel | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
user = await this.systemUserModel
|
user = await this.systemUserModel
|
||||||
@@ -56,8 +62,14 @@ export class AuthSigninService {
|
|||||||
.findOne({ email: payload.sub })
|
.findOne({ email: payload.sub })
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
tenant = await this.tenantModel
|
||||||
|
.query()
|
||||||
|
.findById(user.tenantId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
this.clsService.set('tenantId', user.tenantId);
|
this.clsService.set('tenantId', user.tenantId);
|
||||||
this.clsService.set('userId', user.id);
|
this.clsService.set('userId', user.id);
|
||||||
|
this.clsService.set('organizationId', tenant.organizationId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new UserNotFoundException(String(payload.sub));
|
throw new UserNotFoundException(String(payload.sub));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
|||||||
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
|
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
|
||||||
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
||||||
import { BillMeta } from './Bill.meta';
|
import { BillMeta } from './Bill.meta';
|
||||||
|
import { sanitizeSortDirection } from '@/modules/DynamicListing/DynamicFilter/sanitizeSortDirection';
|
||||||
import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator';
|
import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator';
|
||||||
import { BillDefaultViews } from '../Bills.constants';
|
import { BillDefaultViews } from '../Bills.constants';
|
||||||
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
|
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
|
||||||
@@ -407,7 +408,8 @@ export class Bill extends TenantBaseModel {
|
|||||||
* Sort the bills by full-payment bills.
|
* Sort the bills by full-payment bills.
|
||||||
*/
|
*/
|
||||||
sortByStatus(query, order) {
|
sortByStatus(query, order) {
|
||||||
query.orderByRaw(`PAYMENT_AMOUNT = AMOUNT ${order}`);
|
const dir = sanitizeSortDirection(order);
|
||||||
|
query.orderByRaw(`PAYMENT_AMOUNT = AMOUNT ${dir}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/Inje
|
|||||||
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
|
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
|
||||||
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
|
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
|
||||||
import { CreditNoteMeta } from './CreditNote.meta';
|
import { CreditNoteMeta } from './CreditNote.meta';
|
||||||
|
import { sanitizeSortDirection } from '@/modules/DynamicListing/DynamicFilter/sanitizeSortDirection';
|
||||||
import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator';
|
import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator';
|
||||||
import { CreditNoteDefaultViews } from '../constants';
|
import { CreditNoteDefaultViews } from '../constants';
|
||||||
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
|
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
|
||||||
@@ -277,8 +278,9 @@ export class CreditNote extends TenantBaseModel {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
sortByStatus(query, order) {
|
sortByStatus(query, order) {
|
||||||
|
const dir = sanitizeSortDirection(order);
|
||||||
query.orderByRaw(
|
query.orderByRaw(
|
||||||
`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICES_AMOUNT) = COALESCE(AMOUNT) ${order}`,
|
`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICES_AMOUNT) = COALESCE(AMOUNT) ${dir}`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { IsEmail, IsOptional, IsString } from 'class-validator';
|
import { IsEmail, IsString } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsOptional } from '@/common/decorators/Validators';
|
||||||
|
|
||||||
export class ContactAddressDto {
|
export class ContactAddressDto {
|
||||||
@ApiProperty({ required: false, description: 'Billing address line 1' })
|
@ApiProperty({ required: false, description: 'Billing address line 1' })
|
||||||
|
|||||||
@@ -155,4 +155,13 @@ export class CreateCustomerDto extends ContactAddressDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
required: false,
|
||||||
|
description: 'Customer code',
|
||||||
|
example: 'CUST-001',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
import { IsBoolean, IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { ContactAddressDto } from './ContactAddress.dto';
|
import { ContactAddressDto } from './ContactAddress.dto';
|
||||||
|
import { IsOptional } from '@/common/decorators/Validators';
|
||||||
|
|
||||||
export class EditCustomerDto extends ContactAddressDto {
|
export class EditCustomerDto extends ContactAddressDto {
|
||||||
@ApiProperty({ required: true, description: 'Customer type' })
|
@ApiProperty({ required: true, description: 'Customer type' })
|
||||||
@@ -62,4 +63,9 @@ export class EditCustomerDto extends ContactAddressDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ required: false, description: 'Customer code' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ export class Customer extends TenantBaseModel {
|
|||||||
note: string;
|
note: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
|
||||||
|
code?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query builder.
|
* Query builder.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export interface ICustomerNewDTO extends IContactAddressDTO {
|
|||||||
|
|
||||||
note?: string;
|
note?: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICustomerEditDTO extends IContactAddressDTO {
|
export interface ICustomerEditDTO extends IContactAddressDTO {
|
||||||
@@ -50,6 +51,7 @@ export interface ICustomerEditDTO extends IContactAddressDTO {
|
|||||||
|
|
||||||
note?: string;
|
note?: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICustomersFilter extends IDynamicListFilter {
|
export interface ICustomersFilter extends IDynamicListFilter {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { FIELD_TYPE } from './constants';
|
import { FIELD_TYPE } from './constants';
|
||||||
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
|
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
|
||||||
|
import { sanitizeSortDirection } from './sanitizeSortDirection';
|
||||||
|
|
||||||
interface ISortRole {
|
interface ISortRole {
|
||||||
fieldKey: string;
|
fieldKey: string;
|
||||||
@@ -67,17 +68,18 @@ export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
|
|||||||
public buildQuery = () => {
|
public buildQuery = () => {
|
||||||
const field = this.model.getField(this.sortRole.fieldKey);
|
const field = this.model.getField(this.sortRole.fieldKey);
|
||||||
const comparatorColumn = this.getFieldComparatorColumn(field);
|
const comparatorColumn = this.getFieldComparatorColumn(field);
|
||||||
|
const safeOrder = sanitizeSortDirection(this.sortRole.order);
|
||||||
|
|
||||||
// Sort custom query.
|
// Sort custom query.
|
||||||
if (typeof field.sortCustomQuery !== 'undefined') {
|
if (typeof field.sortCustomQuery !== 'undefined') {
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
field.sortCustomQuery(builder, this.sortRole);
|
field.sortCustomQuery(builder, { ...this.sortRole, order: safeOrder });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
if (this.sortRole.fieldKey) {
|
if (this.sortRole.fieldKey) {
|
||||||
builder.orderBy(`${comparatorColumn}`, this.sortRole.order);
|
builder.orderBy(`${comparatorColumn}`, safeOrder);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Normalises an arbitrary `sortOrder` value to a SQL-safe direction.
|
||||||
|
* Returns 'DESC' only on an explicit case-insensitive match; otherwise 'ASC'.
|
||||||
|
* Used to defuse `orderByRaw` interpolation in dynamic listing modifiers.
|
||||||
|
*/
|
||||||
|
export function sanitizeSortDirection(order: unknown): 'ASC' | 'DESC' {
|
||||||
|
return String(order ?? '').toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
|
||||||
|
}
|
||||||
@@ -1,9 +1,21 @@
|
|||||||
import { ToNumber } from '@/common/decorators/Validators';
|
import { ToNumber } from '@/common/decorators/Validators';
|
||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import { IsArray, IsOptional, IsString } from 'class-validator';
|
import { IsArray, IsIn, IsInt, IsOptional, IsString } from 'class-validator';
|
||||||
import { IFilterRole, ISortOrder } from '../DynamicFilter/DynamicFilter.types';
|
import { IFilterRole, ISortOrder } from '../DynamicFilter/DynamicFilter.types';
|
||||||
|
|
||||||
export class DynamicFilterQueryDto {
|
export class DynamicFilterQueryDto {
|
||||||
|
@ApiPropertyOptional({ description: 'Page number (1-based)', type: Number })
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@ToNumber()
|
||||||
|
page?: number;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ description: 'Page size', type: Number })
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@ToNumber()
|
||||||
|
pageSize?: number;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: 'Custom view ID', type: Number })
|
@ApiPropertyOptional({ description: 'Custom view ID', type: Number })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ToNumber()
|
@ToNumber()
|
||||||
@@ -20,7 +32,7 @@ export class DynamicFilterQueryDto {
|
|||||||
columnSortBy: string;
|
columnSortBy: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: 'Sort order (asc/desc)', type: String })
|
@ApiPropertyOptional({ description: 'Sort order (asc/desc)', type: String })
|
||||||
@IsString()
|
@IsIn(['ASC', 'DESC', 'asc', 'desc'])
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
sortOrder: ISortOrder;
|
sortOrder: ISortOrder;
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { ServiceError } from '../Items/ServiceError';
|
|||||||
import { ResourceService } from '../Resource/ResourceService';
|
import { ResourceService } from '../Resource/ResourceService';
|
||||||
import { getExportableService } from './decorators/ExportableModel.decorator';
|
import { getExportableService } from './decorators/ExportableModel.decorator';
|
||||||
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
|
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
|
||||||
|
import { I18nService } from 'nestjs-i18n';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportResourceService {
|
export class ExportResourceService {
|
||||||
@@ -20,6 +21,7 @@ export class ExportResourceService {
|
|||||||
private readonly exportPdf: ExportPdf,
|
private readonly exportPdf: ExportPdf,
|
||||||
private readonly resourceService: ResourceService,
|
private readonly resourceService: ResourceService,
|
||||||
private readonly moduleRef: ModuleRef,
|
private readonly moduleRef: ModuleRef,
|
||||||
|
private readonly i18nService: I18nService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,7 +149,7 @@ export class ExportResourceService {
|
|||||||
const group = parent;
|
const group = parent;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: value.name,
|
name: this.i18nService.t(value.name, { defaultValue: value.name }),
|
||||||
type: value.type || 'text',
|
type: value.type || 'text',
|
||||||
accessor: value.accessor || key,
|
accessor: value.accessor || key,
|
||||||
group,
|
group,
|
||||||
@@ -174,7 +176,7 @@ export class ExportResourceService {
|
|||||||
const group = parent;
|
const group = parent;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: value.name,
|
name: this.i18nService.t(value.name, { defaultValue: value.name }),
|
||||||
type: value.type || 'text',
|
type: value.type || 'text',
|
||||||
accessor: value.accessor || key,
|
accessor: value.accessor || key,
|
||||||
group,
|
group,
|
||||||
|
|||||||
+1
@@ -38,6 +38,7 @@ export class APAgingSummarySheet extends AgingSummaryReport {
|
|||||||
|
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
this.baseCurrency = meta.baseCurrency;
|
||||||
this.numberFormat = this.query.numberFormat;
|
this.numberFormat = this.query.numberFormat;
|
||||||
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
|
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
|
||||||
|
|
||||||
|
|||||||
+1
@@ -44,6 +44,7 @@ export class ARAgingSummarySheet extends AgingSummaryReport {
|
|||||||
|
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
this.baseCurrency = meta.baseCurrency;
|
||||||
this.numberFormat = this.query.numberFormat;
|
this.numberFormat = this.query.numberFormat;
|
||||||
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
|
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ import { IARAgingSummaryCustomer } from '../ARAgingSummary/ARAgingSummary.types'
|
|||||||
export abstract class AgingSummaryReport extends AgingReport {
|
export abstract class AgingSummaryReport extends AgingReport {
|
||||||
readonly contacts: ModelObject<Customer | Vendor>[];
|
readonly contacts: ModelObject<Customer | Vendor>[];
|
||||||
readonly agingPeriods: IAgingPeriod[] = [];
|
readonly agingPeriods: IAgingPeriod[] = [];
|
||||||
readonly baseCurrency: string;
|
public baseCurrency: string;
|
||||||
readonly query: IAgingSummaryQuery;
|
readonly query: IAgingSummaryQuery;
|
||||||
readonly overdueInvoicesByContactId: Record<
|
readonly overdueInvoicesByContactId: Record<
|
||||||
number,
|
number,
|
||||||
|
|||||||
+3
-3
@@ -281,7 +281,7 @@ export const BalanceSheetResponseExample = {
|
|||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'Current Liabilties',
|
name: 'Current Liabilities',
|
||||||
id: 'CURRENT_LIABILITY',
|
id: 'CURRENT_LIABILITY',
|
||||||
node_type: 'AGGREGATE',
|
node_type: 'AGGREGATE',
|
||||||
type: 'AGGREGATE',
|
type: 'AGGREGATE',
|
||||||
@@ -912,7 +912,7 @@ export const BalanceSheetTableResponseExample = {
|
|||||||
cells: [
|
cells: [
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
value: 'Current Liabilties',
|
value: 'Current Liabilities',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'total',
|
key: 'total',
|
||||||
@@ -1024,7 +1024,7 @@ export const BalanceSheetTableResponseExample = {
|
|||||||
cells: [
|
cells: [
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
value: 'Total Current Liabilties',
|
value: 'Total Current Liabilities',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'total',
|
key: 'total',
|
||||||
|
|||||||
+1
-1
@@ -88,7 +88,7 @@ export const getBalanceSheetSchema = () => [
|
|||||||
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE,
|
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.AGGREGATE,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'balance_sheet.current_liabilties',
|
name: 'balance_sheet.current_liabilities',
|
||||||
id: BALANCE_SHEET_SCHEMA_NODE_ID.CURRENT_LIABILITY,
|
id: BALANCE_SHEET_SCHEMA_NODE_ID.CURRENT_LIABILITY,
|
||||||
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
|
type: BALANCE_SHEET_SCHEMA_NODE_TYPE.ACCOUNTS,
|
||||||
accountsTypes: [
|
accountsTypes: [
|
||||||
|
|||||||
+1
@@ -33,6 +33,7 @@ export class InventoryValuationSheet extends FinancialSheet {
|
|||||||
|
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
this.baseCurrency = meta.baseCurrency;
|
||||||
this.numberFormat = this.query.numberFormat;
|
this.numberFormat = this.query.numberFormat;
|
||||||
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
|
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -31,6 +31,7 @@ export class SalesTaxLiabilitySummary extends FinancialSheet {
|
|||||||
|
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
this.baseCurrency = meta.baseCurrency;
|
||||||
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
|
this.dateFormat = meta.dateFormat || DEFAULT_REPORT_META.dateFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ export class VendorBalanceSummaryService {
|
|||||||
const reportInstance = new VendorBalanceSummaryReport(
|
const reportInstance = new VendorBalanceSummaryReport(
|
||||||
this.vendorBalanceSummaryRepository,
|
this.vendorBalanceSummaryRepository,
|
||||||
filter,
|
filter,
|
||||||
{ baseCurrency: this.vendorBalanceSummaryRepository.baseCurrency, dateFormat: meta.dateFormat },
|
{ baseCurrency: meta.baseCurrency, dateFormat: meta.dateFormat },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Triggers `onVendorBalanceSummaryViewed` event.
|
// Triggers `onVendorBalanceSummaryViewed` event.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
|||||||
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
|
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
|
||||||
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
||||||
import { ManualJournalMeta } from './ManualJournal.meta';
|
import { ManualJournalMeta } from './ManualJournal.meta';
|
||||||
|
import { sanitizeSortDirection } from '@/modules/DynamicListing/DynamicFilter/sanitizeSortDirection';
|
||||||
import { ImportableModel } from '@/modules/Import/decorators/Import.decorator';
|
import { ImportableModel } from '@/modules/Import/decorators/Import.decorator';
|
||||||
import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator';
|
import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator';
|
||||||
import { ManualJournalDefaultViews } from '../constants';
|
import { ManualJournalDefaultViews } from '../constants';
|
||||||
@@ -80,7 +81,8 @@ export class ManualJournal extends TenantBaseModel {
|
|||||||
* Sort by status query.
|
* Sort by status query.
|
||||||
*/
|
*/
|
||||||
sortByStatus(query, order) {
|
sortByStatus(query, order) {
|
||||||
query.orderByRaw(`PUBLISHED_AT IS NULL ${order}`);
|
const dir = sanitizeSortDirection(order);
|
||||||
|
query.orderByRaw(`PUBLISHED_AT IS NULL ${dir}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { BillAction } from "../Bills/Bills.types";
|
|||||||
import { AbilitySubject, ISubjectAbilitiesSchema, ISubjectAbilitySchema } from "./Roles.types";
|
import { AbilitySubject, ISubjectAbilitiesSchema, ISubjectAbilitySchema } from "./Roles.types";
|
||||||
import { PaymentReceiveAction } from "../PaymentReceived/types/PaymentReceived.types";
|
import { PaymentReceiveAction } from "../PaymentReceived/types/PaymentReceived.types";
|
||||||
import { PreferencesAction } from "../Settings/Settings.types";
|
import { PreferencesAction } from "../Settings/Settings.types";
|
||||||
|
import { AttachmentAction } from "../Attachments/Attachments.types";
|
||||||
|
|
||||||
export const AbilitySchema: ISubjectAbilitiesSchema[] = [
|
export const AbilitySchema: ISubjectAbilitiesSchema[] = [
|
||||||
{
|
{
|
||||||
@@ -305,6 +306,14 @@ export const AbilitySchema: ISubjectAbilitiesSchema[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
subject: AbilitySubject.Attachment,
|
||||||
|
subjectLabel: 'ability.attachments',
|
||||||
|
abilities: [
|
||||||
|
{ key: AttachmentAction.View, label: 'ability.view', default: true },
|
||||||
|
{ key: AttachmentAction.Delete, label: 'ability.delete', default: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ export enum AbilitySubject {
|
|||||||
CreditNote = 'CreditNode',
|
CreditNote = 'CreditNode',
|
||||||
VendorCredit = 'VendorCredit',
|
VendorCredit = 'VendorCredit',
|
||||||
Project = 'Project',
|
Project = 'Project',
|
||||||
TaxRate = 'TaxRate'
|
TaxRate = 'TaxRate',
|
||||||
|
Attachment = 'Attachment',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRoleCreatedPayload {
|
export interface IRoleCreatedPayload {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.dec
|
|||||||
import { ImportableModel } from '@/modules/Import/decorators/Import.decorator';
|
import { ImportableModel } from '@/modules/Import/decorators/Import.decorator';
|
||||||
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
||||||
import { SaleEstimateMeta } from './SaleEstimate.meta';
|
import { SaleEstimateMeta } from './SaleEstimate.meta';
|
||||||
|
import { sanitizeSortDirection } from '@/modules/DynamicListing/DynamicFilter/sanitizeSortDirection';
|
||||||
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
|
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
|
||||||
import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
|
import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
|
||||||
import { Customer } from '@/modules/Customers/models/Customer';
|
import { Customer } from '@/modules/Customers/models/Customer';
|
||||||
@@ -250,7 +251,8 @@ export class SaleEstimate extends TenantBaseModel {
|
|||||||
* Sorting the estimates orders by delivery status.
|
* Sorting the estimates orders by delivery status.
|
||||||
*/
|
*/
|
||||||
orderByStatus(query, order) {
|
orderByStatus(query, order) {
|
||||||
query.orderByRaw(`delivered_at is null ${order}`);
|
const dir = sanitizeSortDirection(order);
|
||||||
|
query.orderByRaw(`delivered_at is null ${dir}`);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Filtering the estimates oreders by status field.
|
* Filtering the estimates oreders by status field.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
|
|||||||
import { DiscountType } from '@/common/types/Discount';
|
import { DiscountType } from '@/common/types/Discount';
|
||||||
import { Account } from '@/modules/Accounts/models/Account.model';
|
import { Account } from '@/modules/Accounts/models/Account.model';
|
||||||
import { ISearchRole } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
|
import { ISearchRole } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
|
||||||
|
import { sanitizeSortDirection } from '@/modules/DynamicListing/DynamicFilter/sanitizeSortDirection';
|
||||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||||
import { TransactionPaymentServiceEntry } from '@/modules/PaymentServices/models/TransactionPaymentServiceEntry.model';
|
import { TransactionPaymentServiceEntry } from '@/modules/PaymentServices/models/TransactionPaymentServiceEntry.model';
|
||||||
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
|
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
|
||||||
@@ -417,14 +418,16 @@ export class SaleInvoice extends TenantBaseModel {
|
|||||||
* Sort the sale invoices by full-payment invoices.
|
* Sort the sale invoices by full-payment invoices.
|
||||||
*/
|
*/
|
||||||
sortByStatus(query, order) {
|
sortByStatus(query, order) {
|
||||||
query.orderByRaw(`PAYMENT_AMOUNT = BALANCE ${order}`);
|
const dir = sanitizeSortDirection(order);
|
||||||
|
query.orderByRaw(`PAYMENT_AMOUNT = BALANCE ${dir}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort the sale invoices by the due amount.
|
* Sort the sale invoices by the due amount.
|
||||||
*/
|
*/
|
||||||
sortByDueAmount(query, order) {
|
sortByDueAmount(query, order) {
|
||||||
query.orderByRaw(`BALANCE - PAYMENT_AMOUNT ${order}`);
|
const dir = sanitizeSortDirection(order);
|
||||||
|
query.orderByRaw(`BALANCE - PAYMENT_AMOUNT ${dir}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { ImportableModel } from '@/modules/Import/decorators/Import.decorator';
|
|||||||
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
||||||
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
|
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
|
||||||
import { SaleReceiptMeta } from './SaleReceipt.meta';
|
import { SaleReceiptMeta } from './SaleReceipt.meta';
|
||||||
|
import { sanitizeSortDirection } from '@/modules/DynamicListing/DynamicFilter/sanitizeSortDirection';
|
||||||
import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator';
|
import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator';
|
||||||
import { SaleReceiptDefaultViews } from '../constants';
|
import { SaleReceiptDefaultViews } from '../constants';
|
||||||
|
|
||||||
@@ -238,7 +239,8 @@ export class SaleReceipt extends ExtendedModel {
|
|||||||
* Sorting the receipts order by status.
|
* Sorting the receipts order by status.
|
||||||
*/
|
*/
|
||||||
sortByStatus(query, order) {
|
sortByStatus(query, order) {
|
||||||
query.orderByRaw(`CLOSED_AT IS NULL ${order}`);
|
const dir = sanitizeSortDirection(order);
|
||||||
|
query.orderByRaw(`CLOSED_AT IS NULL ${dir}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ export const TenantAgnosticRoute = () => SetMetadata(IS_TENANT_AGNOSTIC, true);
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class TenancyGlobalGuard implements CanActivate {
|
export class TenancyGlobalGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
private reflector: Reflector,
|
|
||||||
private readonly cls: ClsService,
|
|
||||||
|
|
||||||
@Inject(UserTenant.name)
|
@Inject(UserTenant.name)
|
||||||
private readonly userTenantModel: typeof UserTenant,
|
private readonly userTenantModel: typeof UserTenant,
|
||||||
|
|
||||||
@Inject(TenantModel.name)
|
@Inject(TenantModel.name)
|
||||||
private readonly tenantModel: typeof TenantModel,
|
private readonly tenantModel: typeof TenantModel,
|
||||||
|
|
||||||
|
private readonly reflector: Reflector,
|
||||||
|
private readonly clsService: ClsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,9 +55,8 @@ export class TenancyGlobalGuard implements CanActivate {
|
|||||||
if (!organizationId) {
|
if (!organizationId) {
|
||||||
throw new UnauthorizedException('Organization ID is required.');
|
throw new UnauthorizedException('Organization ID is required.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that the authenticated user is a member of the requested organization.
|
// Validate that the authenticated user is a member of the requested organization.
|
||||||
const userId = this.cls.get<number>('userId');
|
const userId = this.clsService.get<number>('userId');
|
||||||
|
|
||||||
const tenant = await this.tenantModel.query().findOne({ organizationId });
|
const tenant = await this.tenantModel.query().findOne({ organizationId });
|
||||||
if (!tenant) {
|
if (!tenant) {
|
||||||
@@ -73,7 +72,6 @@ export class TenancyGlobalGuard implements CanActivate {
|
|||||||
'You do not have access to this organization.',
|
'You do not have access to this organization.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.dec
|
|||||||
import { ImportableModel } from '@/modules/Import/decorators/Import.decorator';
|
import { ImportableModel } from '@/modules/Import/decorators/Import.decorator';
|
||||||
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
|
||||||
import { VendorCreditMeta } from './VendorCredit.meta';
|
import { VendorCreditMeta } from './VendorCredit.meta';
|
||||||
|
import { sanitizeSortDirection } from '@/modules/DynamicListing/DynamicFilter/sanitizeSortDirection';
|
||||||
import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator';
|
import { InjectModelDefaultViews } from '@/modules/Views/decorators/InjectModelDefaultViews.decorator';
|
||||||
import { VendorCreditDefaultViews } from '../constants';
|
import { VendorCreditDefaultViews } from '../constants';
|
||||||
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
|
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
|
||||||
@@ -198,8 +199,9 @@ export class VendorCredit extends TenantBaseModel {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
sortByStatus(query, order) {
|
sortByStatus(query, order) {
|
||||||
|
const dir = sanitizeSortDirection(order);
|
||||||
query.orderByRaw(
|
query.orderByRaw(
|
||||||
`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICED_AMOUNT) = COALESCE(AMOUNT) ${order}`,
|
`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICED_AMOUNT) = COALESCE(AMOUNT) ${dir}`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -115,4 +115,13 @@ export class CreateVendorDto extends ContactAddressDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
required: false,
|
||||||
|
description: 'Vendor code',
|
||||||
|
example: 'VEND-001',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ContactAddressDto } from '@/modules/Customers/dtos/ContactAddress.dto';
|
import { ContactAddressDto } from '@/modules/Customers/dtos/ContactAddress.dto';
|
||||||
import { IsEmail, IsString, IsBoolean, IsOptional } from 'class-validator';
|
import { IsEmail, IsString, IsBoolean } from 'class-validator';
|
||||||
|
import { IsOptional } from '@/common/decorators/Validators';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class EditVendorDto extends ContactAddressDto {
|
export class EditVendorDto extends ContactAddressDto {
|
||||||
@@ -60,4 +61,9 @@ export class EditVendorDto extends ContactAddressDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ required: false, description: 'Vendor code' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ export class Vendor extends TenantBaseModel {
|
|||||||
note: string;
|
note: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
|
||||||
|
code?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query builder.
|
* Query builder.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export interface IVendorNewDTO extends IContactAddressDTO {
|
|||||||
|
|
||||||
note?: string;
|
note?: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
code?: string;
|
||||||
}
|
}
|
||||||
export interface IVendorEditDTO extends IContactAddressDTO {
|
export interface IVendorEditDTO extends IContactAddressDTO {
|
||||||
salutation?: string;
|
salutation?: string;
|
||||||
@@ -46,6 +47,7 @@ export interface IVendorEditDTO extends IContactAddressDTO {
|
|||||||
|
|
||||||
note?: string;
|
note?: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVendorsFilter extends IDynamicListFilter {
|
export interface IVendorsFilter extends IDynamicListFilter {
|
||||||
|
|||||||
@@ -77,4 +77,16 @@ describe('Expenses (e2e)', () => {
|
|||||||
.set('Authorization', AuthorizationHeader)
|
.set('Authorization', AuthorizationHeader)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('/expenses (GET) honors page and pageSize query params', async () => {
|
||||||
|
const response = await request(app.getHttpServer())
|
||||||
|
.get('/expenses?page=2&pageSize=5')
|
||||||
|
.set('organization-id', orgainzationId)
|
||||||
|
.set('Authorization', AuthorizationHeader)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.pagination).toBeDefined();
|
||||||
|
expect(response.body.pagination.page).toBe(2);
|
||||||
|
expect(response.body.pagination.page_size).toBe(5);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -138,6 +138,8 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "cross-env PORT=4173 vite preview",
|
"preview": "cross-env PORT=4173 vite preview",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
|
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,md}\"",
|
||||||
|
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,md}\"",
|
||||||
"test": "node scripts/test.js",
|
"test": "node scripts/test.js",
|
||||||
"storybook": "start-storybook -p 6006"
|
"storybook": "start-storybook -p 6006"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import { FSelect } from '../Forms';
|
|||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
export type DisplayNameListItem = { label: string };
|
export type DisplayNameListItem = { label: string };
|
||||||
|
type DisplayNameFormat = {
|
||||||
|
format: string;
|
||||||
|
values: Array<string | undefined>;
|
||||||
|
required: number[];
|
||||||
|
};
|
||||||
|
|
||||||
export interface DisplayNameListProps
|
export interface DisplayNameListProps
|
||||||
extends Omit<
|
extends Omit<
|
||||||
@@ -11,6 +16,47 @@ export interface DisplayNameListProps
|
|||||||
'items' | 'valueAccessor' | 'textAccessor' | 'labelAccessor'
|
'items' | 'valueAccessor' | 'textAccessor' | 'labelAccessor'
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
|
function useDisplayNameFormatOptions(
|
||||||
|
salutation?: string,
|
||||||
|
firstName?: string,
|
||||||
|
lastName?: string,
|
||||||
|
companyName?: string,
|
||||||
|
): DisplayNameListItem[] {
|
||||||
|
return useMemo(() => {
|
||||||
|
const formats: DisplayNameFormat[] = [
|
||||||
|
{
|
||||||
|
format: '{1} {2} {3}',
|
||||||
|
values: [salutation, firstName, lastName],
|
||||||
|
required: [1],
|
||||||
|
},
|
||||||
|
{ format: '{1} {2}', values: [firstName, lastName], required: [] },
|
||||||
|
{ format: '{1}, {2}', values: [firstName, lastName], required: [1, 2] },
|
||||||
|
{ format: '{1}', values: [companyName], required: [1] },
|
||||||
|
];
|
||||||
|
|
||||||
|
return formats
|
||||||
|
.filter(
|
||||||
|
(format) =>
|
||||||
|
!format.values.some((value, index) => {
|
||||||
|
return !value && format.required.indexOf(index + 1) !== -1;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.map((formatOption) => {
|
||||||
|
const { format, values } = formatOption;
|
||||||
|
let label = format;
|
||||||
|
|
||||||
|
values.forEach((value, index) => {
|
||||||
|
const replaceWith = value || '';
|
||||||
|
label = label.replace(`{${index + 1}}`, replaceWith).trim();
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
label: label.replace(/\s+/g, ' ').replace(/\s+,/g, ',').trim(),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(({ label }) => Boolean(label));
|
||||||
|
}, [salutation, firstName, lastName, companyName]);
|
||||||
|
}
|
||||||
|
|
||||||
export function DisplayNameList({ ...restProps }: DisplayNameListProps) {
|
export function DisplayNameList({ ...restProps }: DisplayNameListProps) {
|
||||||
const {
|
const {
|
||||||
values: {
|
values: {
|
||||||
@@ -21,40 +67,11 @@ export function DisplayNameList({ ...restProps }: DisplayNameListProps) {
|
|||||||
},
|
},
|
||||||
} = useFormikContext<any>();
|
} = useFormikContext<any>();
|
||||||
|
|
||||||
const formats = useMemo(
|
const formatOptions = useDisplayNameFormatOptions(
|
||||||
() => [
|
salutation,
|
||||||
{
|
firstName,
|
||||||
format: '{1} {2} {3}',
|
lastName,
|
||||||
values: [salutation, firstName, lastName],
|
companyName,
|
||||||
required: [1],
|
|
||||||
},
|
|
||||||
{ format: '{1} {2}', values: [firstName, lastName], required: [] },
|
|
||||||
{ format: '{1}, {2}', values: [firstName, lastName], required: [1, 2] },
|
|
||||||
{ format: '{1}', values: [companyName], required: [1] },
|
|
||||||
],
|
|
||||||
[firstName, lastName, companyName, salutation],
|
|
||||||
);
|
|
||||||
|
|
||||||
const formatOptions: DisplayNameListItem[] = useMemo(
|
|
||||||
() =>
|
|
||||||
formats
|
|
||||||
.filter(
|
|
||||||
(format) =>
|
|
||||||
!format.values.some((value, index) => {
|
|
||||||
return !value && format.required.indexOf(index + 1) !== -1;
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.map((formatOption) => {
|
|
||||||
const { format, values } = formatOption;
|
|
||||||
let label = format;
|
|
||||||
|
|
||||||
values.forEach((value, index) => {
|
|
||||||
const replaceWith = value || '';
|
|
||||||
label = label.replace(`{${index + 1}}`, replaceWith).trim();
|
|
||||||
});
|
|
||||||
return { label: label.replace(/\s+/g, ' ') };
|
|
||||||
}),
|
|
||||||
[formats],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -62,6 +79,7 @@ export function DisplayNameList({ ...restProps }: DisplayNameListProps) {
|
|||||||
items={formatOptions}
|
items={formatOptions}
|
||||||
valueAccessor={'label'}
|
valueAccessor={'label'}
|
||||||
textAccessor={'label'}
|
textAccessor={'label'}
|
||||||
|
labelAccessor={'_label'}
|
||||||
placeholder={intl.get('select_display_name_as')}
|
placeholder={intl.get('select_display_name_as')}
|
||||||
filterable={false}
|
filterable={false}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export function SalutationList({ ...restProps }: SalutationListProps) {
|
|||||||
items={items}
|
items={items}
|
||||||
valueAccessor={'key'}
|
valueAccessor={'key'}
|
||||||
textAccessor={'label'}
|
textAccessor={'label'}
|
||||||
|
labelAccessor={'_label'}
|
||||||
placeholder={intl.get('salutation')}
|
placeholder={intl.get('salutation')}
|
||||||
filterable={false}
|
filterable={false}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|||||||
+7
-2
@@ -35,11 +35,16 @@ function UncategorizeBankTransactionsBulkAlert({
|
|||||||
uncategorizeTransactions({ ids: uncategorizeTransactionsIds })
|
uncategorizeTransactions({ ids: uncategorizeTransactionsIds })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'The bank feeds of the bank account has been resumed.',
|
message: 'The selected transactions have been uncategorized.',
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {})
|
.catch((error) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Something went wrong while uncategorizing transactions.',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,153 +1,17 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Row, Col } from '@/components';
|
import { Row } from '@/components';
|
||||||
import {
|
|
||||||
FormattedMessage as T,
|
|
||||||
FFormGroup,
|
|
||||||
FInputGroup,
|
|
||||||
FTextArea,
|
|
||||||
} from '@/components';
|
|
||||||
|
|
||||||
const CustomerBillingAddress = ({}) => {
|
import CustomerBillingAddress from './CustomerBillingAddress';
|
||||||
|
import CustomerShippingAddress from './CustomerShippingAddress';
|
||||||
|
|
||||||
|
export default function CustomerAddressTabs() {
|
||||||
return (
|
return (
|
||||||
<div className={'tab-panel--address'}>
|
<div className={'tab-panel--address'}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={6}>
|
<CustomerBillingAddress />
|
||||||
<h4>
|
<CustomerShippingAddress />
|
||||||
<T id={'billing_address'} />
|
|
||||||
</h4>
|
|
||||||
{/*------------ Billing Address country -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'billing_address_country'}
|
|
||||||
inline={true}
|
|
||||||
label={<T id={'country'} />}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'billing_address_country'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------ Billing Address 1 -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'billing_address1'}
|
|
||||||
label={<T id={'address_line_1'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FTextArea name={'billing_address1'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------ Billing Address 2 -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'billing_address2'}
|
|
||||||
label={<T id={'address_line_2'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FTextArea name={'billing_address2'} />
|
|
||||||
</FFormGroup>
|
|
||||||
{/*------------ Billing Address city -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'billing_address_city'}
|
|
||||||
label={<T id={'city_town'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'billing_address_city'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------ Billing Address state -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'billing_address_state'}
|
|
||||||
label={<T id={'state'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'billing_address_state'} />
|
|
||||||
</FFormGroup>
|
|
||||||
{/*------------ Billing Address postcode -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'billing_address_postcode'}
|
|
||||||
label={<T id={'zip_code'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'billing_address_postcode'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------ Billing Address phone -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'billing_address_phone'}
|
|
||||||
label={<T id={'phone'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'billing_address_phone'} />
|
|
||||||
</FFormGroup>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col xs={6}>
|
|
||||||
<h4>
|
|
||||||
<T id={'shipping_address'} />
|
|
||||||
</h4>
|
|
||||||
{/*------------ Shipping Address country -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'shipping_address_country'}
|
|
||||||
label={<T id={'country'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'shipping_address_country'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------ Shipping Address 1 -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'shipping_address1'}
|
|
||||||
label={<T id={'address_line_1'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FTextArea name={'shipping_address1'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------ Shipping Address 2 -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'shipping_address2'}
|
|
||||||
label={<T id={'address_line_2'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FTextArea name={'shipping_address2'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------ Shipping Address city -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'shipping_address_city'}
|
|
||||||
label={<T id={'city_town'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'shipping_address_city'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------ Shipping Address state -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'shipping_address_state'}
|
|
||||||
label={<T id={'state'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'shipping_address_state'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------ Shipping Address postcode -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'shipping_address_postcode'}
|
|
||||||
label={<T id={'zip_code'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'shipping_address_postcode'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*------------ Shipping Address phone -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'shipping_address_phone'}
|
|
||||||
label={<T id={'phone'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'shipping_address_phone'} />
|
|
||||||
</FFormGroup>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default CustomerBillingAddress;
|
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@/components';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
FFormGroup,
|
||||||
|
FInputGroup,
|
||||||
|
FTextArea,
|
||||||
|
} from '@/components';
|
||||||
|
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
|
||||||
|
|
||||||
|
export function CustomerBillingAddress() {
|
||||||
|
return (
|
||||||
|
<Box data-section-id="billingAddress">
|
||||||
|
<CustomerFormSectionTitle>
|
||||||
|
<T id={'billing_address'} />
|
||||||
|
</CustomerFormSectionTitle>
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address_country'}
|
||||||
|
label={<T id={'country'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'billing_address_country'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address1'}
|
||||||
|
label={<T id={'address_line_1'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FTextArea name={'billing_address1'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address2'}
|
||||||
|
label={<T id={'address_line_2'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FTextArea name={'billing_address2'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address_city'}
|
||||||
|
label={<T id={'city_town'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'billing_address_city'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address_state'}
|
||||||
|
label={<T id={'state'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'billing_address_state'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address_postcode'}
|
||||||
|
label={<T id={'zip_code'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'billing_address_postcode'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address_phone'}
|
||||||
|
label={<T id={'phone'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'billing_address_phone'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
@@ -11,52 +10,36 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { Group, Icon, FormattedMessage as T } from '@/components';
|
import { Group, Icon, FormattedMessage as T } from '@/components';
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
import { safeInvoke } from '@/utils';
|
|
||||||
|
|
||||||
/**
|
export function CustomerFloatingActions() {
|
||||||
* Customer floating actions bar.
|
|
||||||
*/
|
|
||||||
export default function CustomerFloatingActions({ onCancel }) {
|
|
||||||
// Customer form context.
|
// Customer form context.
|
||||||
const { isNewMode, setSubmitPayload } = useCustomerFormContext();
|
const { isNewMode, setSubmitPayload } = useCustomerFormContext() as {
|
||||||
|
isNewMode: boolean;
|
||||||
|
setSubmitPayload: (payload: { noRedirect: boolean }) => void;
|
||||||
|
};
|
||||||
|
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { resetForm, submitForm, isSubmitting } = useFormikContext();
|
const { submitForm, isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
// Handle submit button click.
|
// Handle submit button click.
|
||||||
const handleSubmitBtnClick = (event) => {
|
const handleSubmitBtnClick = (_event: React.MouseEvent<HTMLElement>) => {
|
||||||
setSubmitPayload({ noRedirect: false });
|
setSubmitPayload({ noRedirect: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle cancel button click.
|
|
||||||
const handleCancelBtnClick = (event) => {
|
|
||||||
safeInvoke(onCancel, event);
|
|
||||||
};
|
|
||||||
|
|
||||||
// handle clear button clicl.
|
|
||||||
const handleClearBtnClick = (event) => {
|
|
||||||
resetForm();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle submit & new button click.
|
// Handle submit & new button click.
|
||||||
const handleSubmitAndNewClick = (event) => {
|
const handleSubmitAndNewClick = (_event: React.MouseEvent<HTMLElement>) => {
|
||||||
submitForm();
|
submitForm();
|
||||||
setSubmitPayload({ noRedirect: true });
|
setSubmitPayload({ noRedirect: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<FloatingActionsGroup spacing={10}>
|
||||||
spacing={10}
|
|
||||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
|
||||||
>
|
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{/* ----------- Save and New ----------- */}
|
{/* ----------- Save and New ----------- */}
|
||||||
<SaveButton
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
@@ -73,9 +56,9 @@ export default function CustomerFloatingActions({ onCancel }) {
|
|||||||
/>
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
}
|
}
|
||||||
minimal={true}
|
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
position={Position.BOTTOM_LEFT}
|
position={Position.BOTTOM_RIGHT}
|
||||||
|
minimal
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
@@ -84,24 +67,16 @@ export default function CustomerFloatingActions({ onCancel }) {
|
|||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
</FloatingActionsGroup>
|
||||||
{/* ----------- Clear & Reset----------- */}
|
|
||||||
<Button
|
|
||||||
className={'ml1'}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
onClick={handleClearBtnClick}
|
|
||||||
text={!isNewMode ? <T id={'reset'} /> : <T id={'clear'} />}
|
|
||||||
/>
|
|
||||||
{/* ----------- Cancel ----------- */}
|
|
||||||
<Button
|
|
||||||
className={'ml1'}
|
|
||||||
onClick={handleCancelBtnClick}
|
|
||||||
text={<T id={'cancel'} />}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SaveButton = styled(Button)`
|
const FloatingActionsGroup = styled(Group)`
|
||||||
min-width: 100px;
|
padding: 10px 0;
|
||||||
`;
|
padding-left: 165px;
|
||||||
|
border-top: 1px solid #50555a;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: var(--color-card-background);
|
||||||
|
z-index: 1;
|
||||||
|
`;
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
|
||||||
import { CustomerFormProvider } from './CustomerFormProvider';
|
|
||||||
import CustomerFormFormik from './CustomerFormFormik';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstracted customer form.
|
|
||||||
*/
|
|
||||||
export default function CustomerForm({ customerId }) {
|
|
||||||
return (
|
|
||||||
<CustomerFormProvider customerId={customerId}>
|
|
||||||
<CustomerFormFormik />
|
|
||||||
</CustomerFormProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
+20
-8
@@ -6,34 +6,46 @@ import { FormattedMessage as T, FFormGroup, FInputGroup } from '@/components';
|
|||||||
|
|
||||||
export default function CustomerFormAfterPrimarySection({}) {
|
export default function CustomerFormAfterPrimarySection({}) {
|
||||||
return (
|
return (
|
||||||
<div className={'customer-form__after-primary-section-content'}>
|
<div>
|
||||||
{/*------------ Customer email -----------*/}
|
{/*------------ Customer email -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'email'}
|
name={'email'}
|
||||||
label={<T id={'customer_email'} />}
|
label={<T id={'customer_email'} />}
|
||||||
inline={true}
|
inline
|
||||||
|
fill
|
||||||
>
|
>
|
||||||
<FInputGroup name={'email'} />
|
<FInputGroup name={'email'} fill />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
{/*------------ Phone number -----------*/}
|
{/*------------ Phone number -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'personal_phone'}
|
name={'personal_phone'}
|
||||||
label={<T id={'phone_number'} />}
|
label={<T id={'phone_number'} />}
|
||||||
inline={true}
|
inline
|
||||||
|
fill
|
||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup fill>
|
||||||
<FInputGroup
|
<FInputGroup
|
||||||
name={'personal_phone'}
|
name={'personal_phone'}
|
||||||
placeholder={intl.get('personal')}
|
placeholder={intl.get('personal')}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
<FInputGroup
|
||||||
|
name={'work_phone'}
|
||||||
|
placeholder={intl.get('work')}
|
||||||
|
fill
|
||||||
/>
|
/>
|
||||||
<FInputGroup name={'work_phone'} placeholder={intl.get('work')} />
|
|
||||||
</ControlGroup>
|
</ControlGroup>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
{/*------------ Customer website -----------*/}
|
{/*------------ Customer website -----------*/}
|
||||||
<FFormGroup name={'website'} label={<T id={'website'} />} inline={true}>
|
<FFormGroup
|
||||||
<FInputGroup name={'website'} placeholder={'http://'} />
|
name={'website'}
|
||||||
|
label={<T id={'website'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'website'} placeholder={'http://'} fill />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { ControlGroup, Divider, Icon as BlueprintIcon } from '@blueprintjs/core';
|
||||||
|
import {
|
||||||
|
Hint,
|
||||||
|
FieldRequiredHint,
|
||||||
|
SalutationList,
|
||||||
|
DisplayNameList,
|
||||||
|
FormattedMessage as T,
|
||||||
|
FInputGroup,
|
||||||
|
FFormGroup,
|
||||||
|
Box,
|
||||||
|
Icon,
|
||||||
|
Stack,
|
||||||
|
} from '@/components';
|
||||||
|
import { CustomerTypeRadioField } from './CustomerTypeRadioField';
|
||||||
|
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
|
||||||
|
import { useAutofocus } from '@/hooks';
|
||||||
|
|
||||||
|
export function CustomerFormBasicSection({}) {
|
||||||
|
const firstNameFieldRef = useAutofocus();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box data-section-id="primary">
|
||||||
|
<CustomerFormSectionTitle>Customer details</CustomerFormSectionTitle>
|
||||||
|
|
||||||
|
{/**-----------Customer type. -----------*/}
|
||||||
|
<CustomerTypeRadioField />
|
||||||
|
|
||||||
|
{/**----------- Contact name -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'salutation'}
|
||||||
|
label={<T id={'contact_name'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<ControlGroup fill>
|
||||||
|
<SalutationList
|
||||||
|
name={'salutation'}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
/>
|
||||||
|
<FInputGroup
|
||||||
|
name={'first_name'}
|
||||||
|
placeholder={intl.get('first_name')}
|
||||||
|
inputRef={(ref) => (firstNameFieldRef.current = ref)}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
<FInputGroup
|
||||||
|
name={'last_name'}
|
||||||
|
placeholder={intl.get('last_name')}
|
||||||
|
fill
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'code'}
|
||||||
|
label={'Customer Code'}
|
||||||
|
helperText="Add a unique account number to identify, reference and search for the contact."
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup
|
||||||
|
name={'code'}
|
||||||
|
fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*----------- Company Name -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'company_name'}
|
||||||
|
label={<T id={'company_name'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'company_name'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*----------- Display Name -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'display_name'}
|
||||||
|
label={<T id={'display_name'} />}
|
||||||
|
helperText="This is the name that appears on invoices and emails."
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<DisplayNameList
|
||||||
|
name={'display_name'}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
buttonProps={{ fill: true }}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<Divider style={{ margin: '20px 0' }} />
|
||||||
|
|
||||||
|
{/*------------ Vendor email -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'email'}
|
||||||
|
label={<T id={'vendor_email'} />}
|
||||||
|
inline
|
||||||
|
>
|
||||||
|
<FInputGroup
|
||||||
|
name={'email'}
|
||||||
|
leftIcon={<Icon icon="envelope" />}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------ Phone number -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'work_phone'}
|
||||||
|
className={'form-group--phone-number'}
|
||||||
|
label={<T id={'phone_number'} />}
|
||||||
|
inline={true}
|
||||||
|
>
|
||||||
|
<Stack spacing={10}>
|
||||||
|
<FInputGroup
|
||||||
|
name={'work_phone'}
|
||||||
|
placeholder={intl.get('work')}
|
||||||
|
leftIcon="phone"
|
||||||
|
/>
|
||||||
|
<FInputGroup
|
||||||
|
name={'personal_phone'}
|
||||||
|
placeholder={intl.get('mobile')}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------ Vendor website -----------*/}
|
||||||
|
<FFormGroup name={'website'} label={<T id={'website'} />} inline={true}>
|
||||||
|
<FInputGroup
|
||||||
|
name={'website'}
|
||||||
|
placeholder={'http://'}
|
||||||
|
leftIcon={<BlueprintIcon icon="globe-network" />}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Tab } from "@blueprintjs/core";
|
||||||
|
import { Card, Group } from "@/components";
|
||||||
|
import { Tabs } from "@blueprintjs/core";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { CustomerFloatingActions } from "./CustomerFloatingActions";
|
||||||
|
import { CustomerFormSections } from "./CustomerFormFields";
|
||||||
|
|
||||||
|
export function CustomerFormContent() {
|
||||||
|
const [selectedTabId, setSelectedTabId] = useState('primary');
|
||||||
|
|
||||||
|
const handleTabChange = (tabId: string) => {
|
||||||
|
const sectionId = String(tabId);
|
||||||
|
setSelectedTabId(sectionId);
|
||||||
|
|
||||||
|
const section = document.querySelector(
|
||||||
|
`[data-section-id="${sectionId}"]`,
|
||||||
|
);
|
||||||
|
if (section) {
|
||||||
|
section.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={css`padding-bottom: 0 !important;`}>
|
||||||
|
<Group verticalAlign={'top'} alignItems={'flex-start'} flexWrap={'nowrap'}>
|
||||||
|
<Tabs
|
||||||
|
selectedTabId={selectedTabId}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
className={css`position: sticky; top: 20px;`}
|
||||||
|
vertical
|
||||||
|
>
|
||||||
|
<Tab id={'primary'} title={'Basic'} />
|
||||||
|
<Tab id={'financial'} title={'Financial'} />
|
||||||
|
<Tab id={'billingAddress'} title={'Billing address'} />
|
||||||
|
<Tab id={'shippingAddress'} title={'Shipping address'} />
|
||||||
|
<Tab id={'notes'} title={'Notes'} />
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<CustomerFormSections />
|
||||||
|
</Group>
|
||||||
|
<CustomerFloatingActions />
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Divider } from '@blueprintjs/core';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { Box } from '@/components';
|
||||||
|
|
||||||
|
import { CustomerFormBasicSection } from './CustomerFormBasicSection';
|
||||||
|
import { CustomerFormFinancialSection } from './CustomerFormFinancialSection';
|
||||||
|
import { CustomerBillingAddress } from './CustomerBillingAddress';
|
||||||
|
import { CustomerShippingAddress } from './CustomerShippingAddress';
|
||||||
|
import { CustomerFormNotesSection } from './CustomerFormNotesSection';
|
||||||
|
|
||||||
|
const customerFormSectionDividerClass = css`
|
||||||
|
margin: 20px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function CustomerFormSections() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<CustomerFormBasicSection />
|
||||||
|
<Divider className={customerFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<CustomerFormFinancialSection />
|
||||||
|
<Divider className={customerFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<CustomerBillingAddress />
|
||||||
|
<Divider className={customerFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<CustomerShippingAddress />
|
||||||
|
<Divider className={customerFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<CustomerFormNotesSection />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
+33
-50
@@ -1,8 +1,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import { Position, ControlGroup } from '@blueprintjs/core';
|
||||||
import { FormGroup, Position, Classes, ControlGroup } from '@blueprintjs/core';
|
import { ErrorMessage, useFormikContext } from 'formik';
|
||||||
import { FastField, ErrorMessage, useFormikContext } from 'formik';
|
|
||||||
import { Features } from '@/constants';
|
import { Features } from '@/constants';
|
||||||
import {
|
import {
|
||||||
FFormGroup,
|
FFormGroup,
|
||||||
@@ -11,11 +10,11 @@ import {
|
|||||||
CurrencySelectList,
|
CurrencySelectList,
|
||||||
BranchSelect,
|
BranchSelect,
|
||||||
FeatureCan,
|
FeatureCan,
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
FMoneyInputGroup,
|
FMoneyInputGroup,
|
||||||
ExchangeRateInputGroup,
|
ExchangeRateInputGroup,
|
||||||
FDateInput,
|
FDateInput,
|
||||||
|
Icon,
|
||||||
|
Box,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
import {
|
import {
|
||||||
@@ -24,67 +23,56 @@ import {
|
|||||||
useSetPrimaryBranchToForm,
|
useSetPrimaryBranchToForm,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { useCurrentOrganization } from '@/hooks/state';
|
import { useCurrentOrganization } from '@/hooks/state';
|
||||||
|
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
|
||||||
|
|
||||||
/**
|
export function CustomerFormFinancialSection() {
|
||||||
* Customer financial panel.
|
|
||||||
*/
|
|
||||||
export default function CustomerFinancialPanel() {
|
|
||||||
const { currencies, customerId, branches } = useCustomerFormContext();
|
const { currencies, customerId, branches } = useCustomerFormContext();
|
||||||
|
|
||||||
// Sets the primary branch to form.
|
// Sets the primary branch to form.
|
||||||
useSetPrimaryBranchToForm();
|
useSetPrimaryBranchToForm();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'tab-panel--financial'}>
|
<Box data-section-id="financial">
|
||||||
<Row>
|
<CustomerFormSectionTitle>
|
||||||
<Col xs={6}>
|
<T id={'financial'} />
|
||||||
{/*------------ Currency -----------*/}
|
</CustomerFormSectionTitle>
|
||||||
|
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'currency_code'}
|
name={'currency_code'}
|
||||||
label={<T id={'currency'} />}
|
label={<T id={'currency'} />}
|
||||||
fastField
|
fastField
|
||||||
inline
|
inline
|
||||||
>
|
fill
|
||||||
|
>
|
||||||
<CurrencySelectList
|
<CurrencySelectList
|
||||||
name="currency_code"
|
name="currency_code"
|
||||||
items={currencies}
|
items={currencies}
|
||||||
disabled={customerId}
|
disabled={customerId}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
{/*------------ Opening balance -----------*/}
|
|
||||||
<CustomerOpeningBalanceField />
|
<CustomerOpeningBalanceField />
|
||||||
|
|
||||||
{/*------ Opening Balance Exchange Rate -----*/}
|
|
||||||
<CustomerOpeningBalanceExchangeRateField />
|
<CustomerOpeningBalanceExchangeRateField />
|
||||||
|
|
||||||
{/*------------ Opening balance at -----------*/}
|
|
||||||
<CustomerOpeningBalanceAtField />
|
<CustomerOpeningBalanceAtField />
|
||||||
|
|
||||||
{/*------------ Opening branch -----------*/}
|
|
||||||
<FeatureCan feature={Features.Branches}>
|
<FeatureCan feature={Features.Branches}>
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
label={<T id={'customer.label.opening_branch'} />}
|
label={<T id={'customer.label.opening_branch'} />}
|
||||||
name={'opening_balance_branch_id'}
|
name={'opening_balance_branch_id'}
|
||||||
inline={true}
|
inline
|
||||||
>
|
>
|
||||||
<BranchSelect
|
<BranchSelect
|
||||||
name={'opening_balance_branch_id'}
|
name={'opening_balance_branch_id'}
|
||||||
branches={branches}
|
branches={branches}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
|
fastField
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
</FeatureCan>
|
</FeatureCan>
|
||||||
</Col>
|
</Box>
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Customer opening balance at date field.
|
|
||||||
* @returns {JSX.Element}
|
|
||||||
*/
|
|
||||||
function CustomerOpeningBalanceAtField() {
|
function CustomerOpeningBalanceAtField() {
|
||||||
const { customerId } = useCustomerFormContext();
|
const { customerId } = useCustomerFormContext();
|
||||||
|
|
||||||
@@ -92,10 +80,11 @@ function CustomerOpeningBalanceAtField() {
|
|||||||
if (customerId) return null;
|
if (customerId) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FFormGroup
|
||||||
name={'opening_balance_at'}
|
name={'opening_balance_at'}
|
||||||
label={<T id={'opening_balance_at'} />}
|
label={<T id={'opening_balance_at'} />}
|
||||||
inline={true}
|
inline
|
||||||
|
fill
|
||||||
helperText={<ErrorMessage name="opening_balance_at" />}
|
helperText={<ErrorMessage name="opening_balance_at" />}
|
||||||
>
|
>
|
||||||
<FDateInput
|
<FDateInput
|
||||||
@@ -104,16 +93,15 @@ function CustomerOpeningBalanceAtField() {
|
|||||||
disabled={customerId}
|
disabled={customerId}
|
||||||
formatDate={(date) => date.toLocaleDateString()}
|
formatDate={(date) => date.toLocaleDateString()}
|
||||||
parseDate={(str) => new Date(str)}
|
parseDate={(str) => new Date(str)}
|
||||||
|
inputProps={{
|
||||||
|
leftIcon: <Icon icon={'date-range'} />,
|
||||||
|
}}
|
||||||
fill={true}
|
fill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FFormGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Customer opening balance field.
|
|
||||||
* @returns {JSX.Element}
|
|
||||||
*/
|
|
||||||
function CustomerOpeningBalanceField() {
|
function CustomerOpeningBalanceField() {
|
||||||
const { customerId } = useCustomerFormContext();
|
const { customerId } = useCustomerFormContext();
|
||||||
const { values } = useFormikContext();
|
const { values } = useFormikContext();
|
||||||
@@ -125,15 +113,17 @@ function CustomerOpeningBalanceField() {
|
|||||||
<FFormGroup
|
<FFormGroup
|
||||||
label={<T id={'opening_balance'} />}
|
label={<T id={'opening_balance'} />}
|
||||||
name={'opening_balance'}
|
name={'opening_balance'}
|
||||||
inline={true}
|
inline
|
||||||
shouldUpdate={openingBalanceFieldShouldUpdate}
|
shouldUpdate={openingBalanceFieldShouldUpdate}
|
||||||
shouldUpdateDeps={{ currencyCode: values.currency_code }}
|
shouldUpdateDeps={{ currencyCode: values.currency_code }}
|
||||||
fastField={true}
|
fastField={true}
|
||||||
|
fill
|
||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<InputPrependText text={values.currency_code} />
|
<InputPrependText text={values.currency_code as string} />
|
||||||
<FMoneyInputGroup
|
<FMoneyInputGroup
|
||||||
name={'opening_balance'}
|
name={'opening_balance'}
|
||||||
|
fastField
|
||||||
inputGroupProps={{ fill: true }}
|
inputGroupProps={{ fill: true }}
|
||||||
/>
|
/>
|
||||||
</ControlGroup>
|
</ControlGroup>
|
||||||
@@ -141,11 +131,6 @@ function CustomerOpeningBalanceField() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Customer opening balance exchange rate field if the customer has foreign
|
|
||||||
* currency.
|
|
||||||
* @returns {JSX.Element}
|
|
||||||
*/
|
|
||||||
function CustomerOpeningBalanceExchangeRateField() {
|
function CustomerOpeningBalanceExchangeRateField() {
|
||||||
const { values } = useFormikContext();
|
const { values } = useFormikContext();
|
||||||
const { customerId } = useCustomerFormContext();
|
const { customerId } = useCustomerFormContext();
|
||||||
@@ -158,16 +143,14 @@ function CustomerOpeningBalanceExchangeRateField() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<FFormGroup
|
|
||||||
label={' '}
|
|
||||||
name={'opening_balance_exchange_rate'}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<ExchangeRateInputGroup
|
<ExchangeRateInputGroup
|
||||||
fromCurrency={values.currency_code}
|
fromCurrency={values.currency_code}
|
||||||
toCurrency={currentOrganization.base_currency}
|
toCurrency={currentOrganization.base_currency}
|
||||||
name={'opening_balance_exchange_rate'}
|
name={'opening_balance_exchange_rate'}
|
||||||
|
onRecalcConfirm={() => {}}
|
||||||
|
onCancel={() => {}}
|
||||||
|
formGroupProps={{ label: ' ' }}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,40 +1,95 @@
|
|||||||
// @ts-nocheck
|
import { useMemo } from 'react';
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import classNames from 'classnames';
|
import { Formik, Form, FormikHelpers } from 'formik';
|
||||||
import { Formik, Form } from 'formik';
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
import { CreateCustomerForm, EditCustomerForm } from './CustomerForm.schema';
|
import { CreateCustomerForm, EditCustomerForm } from './CustomerForm.schema';
|
||||||
import { compose, transformToForm, saveInvoke, parseBoolean } from '@/utils';
|
import { compose, transformToForm, saveInvoke, parseBoolean } from '@/utils';
|
||||||
import { useCustomerFormContext } from './CustomerFormProvider';
|
import { useCustomerFormContext } from './CustomerFormProvider';
|
||||||
import { defaultInitialValues } from './utils';
|
import { defaultInitialValues } from './utils';
|
||||||
|
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
import CustomerFormPrimarySection from './CustomerFormPrimarySection';
|
|
||||||
import CustomerFormAfterPrimarySection from './CustomerFormAfterPrimarySection';
|
|
||||||
import CustomersTabs from './CustomersTabs';
|
|
||||||
import CustomerFloatingActions from './CustomerFloatingActions';
|
|
||||||
|
|
||||||
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
||||||
|
import { CustomerFormContent } from './CustomerFormContent';
|
||||||
|
|
||||||
import '@/style/pages/Customers/Form.scss';
|
type CustomerFormValues = {
|
||||||
|
customer_type: string;
|
||||||
|
salutation: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
company_name: string;
|
||||||
|
display_name: string;
|
||||||
|
|
||||||
/**
|
email?: string;
|
||||||
* Customer form.
|
work_phone?: string;
|
||||||
*/
|
personal_phone?: string;
|
||||||
function CustomerFormFormik({
|
website?: string;
|
||||||
|
note?: string;
|
||||||
|
active: boolean | string;
|
||||||
|
|
||||||
|
billing_address_country: string;
|
||||||
|
billing_address1: string;
|
||||||
|
billing_address2: string;
|
||||||
|
billing_address_city: string;
|
||||||
|
billing_address_state: string;
|
||||||
|
billing_address_postcode?: string;
|
||||||
|
billing_address_phone?: string;
|
||||||
|
|
||||||
|
shipping_address_country: string;
|
||||||
|
shipping_address1: string;
|
||||||
|
shipping_address2: string;
|
||||||
|
shipping_address_city: string;
|
||||||
|
shipping_address_state: string;
|
||||||
|
shipping_address_postcode?: string;
|
||||||
|
shipping_address_phone?: string;
|
||||||
|
|
||||||
|
currency_code: string;
|
||||||
|
opening_balance?: string | number;
|
||||||
|
opening_balance_at?: string;
|
||||||
|
opening_balance_exchange_rate?: string;
|
||||||
|
opening_balance_branch_id?: string;
|
||||||
|
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CustomerFormSubmitPayload = {
|
||||||
|
noRedirect?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CustomerFormFormikRootProps = {
|
||||||
|
organization: {
|
||||||
|
base_currency: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// #ownProps
|
||||||
|
initialValues?: Partial<CustomerFormValues>;
|
||||||
|
onSubmitSuccess?: (
|
||||||
|
values: CustomerFormValues,
|
||||||
|
formArgs: FormikHelpers<CustomerFormValues>,
|
||||||
|
submitPayload: CustomerFormSubmitPayload,
|
||||||
|
responseData?: unknown,
|
||||||
|
) => void;
|
||||||
|
onSubmitError?: (
|
||||||
|
values: CustomerFormValues,
|
||||||
|
formArgs: FormikHelpers<CustomerFormValues>,
|
||||||
|
submitPayload: CustomerFormSubmitPayload,
|
||||||
|
errorData?: unknown,
|
||||||
|
) => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EMPTY_INITIAL_VALUES: Partial<CustomerFormValues> = {};
|
||||||
|
|
||||||
|
function CustomerFormFormikRoot({
|
||||||
organization: { base_currency },
|
organization: { base_currency },
|
||||||
|
|
||||||
// #ownProps
|
// #ownProps
|
||||||
initialValues: initialCustomerValues,
|
initialValues: initialCustomerValues = EMPTY_INITIAL_VALUES,
|
||||||
onSubmitSuccess,
|
onSubmitSuccess,
|
||||||
onSubmitError,
|
onSubmitError,
|
||||||
onCancel,
|
// `onCancel` is accepted for compatibility but currently not used.
|
||||||
className,
|
className,
|
||||||
}) {
|
}: CustomerFormFormikRootProps) {
|
||||||
const {
|
const {
|
||||||
customer,
|
customer,
|
||||||
submitPayload,
|
submitPayload,
|
||||||
@@ -44,28 +99,28 @@ function CustomerFormFormik({
|
|||||||
isNewMode,
|
isNewMode,
|
||||||
} = useCustomerFormContext();
|
} = useCustomerFormContext();
|
||||||
|
|
||||||
/**
|
const initialValues = useMemo<CustomerFormValues>(
|
||||||
* Initial values in create and edit mode.
|
|
||||||
*/
|
|
||||||
const initialValues = useMemo(
|
|
||||||
() => ({
|
() => ({
|
||||||
...defaultInitialValues,
|
...defaultInitialValues,
|
||||||
currency_code: base_currency,
|
currency_code: base_currency,
|
||||||
...transformToForm(contactDuplicate || customer, defaultInitialValues),
|
...transformToForm(contactDuplicate ?? customer ?? {}, defaultInitialValues),
|
||||||
...transformToForm(initialCustomerValues, defaultInitialValues),
|
...transformToForm(initialCustomerValues, defaultInitialValues),
|
||||||
}),
|
}) as CustomerFormValues,
|
||||||
[customer, contactDuplicate, base_currency, initialCustomerValues],
|
[customer, contactDuplicate, base_currency, initialCustomerValues],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handles the form submit.
|
// Handles the form submit.
|
||||||
const handleFormSubmit = (values, formArgs) => {
|
const handleFormSubmit = (
|
||||||
|
values: CustomerFormValues,
|
||||||
|
formArgs: FormikHelpers<CustomerFormValues>,
|
||||||
|
) => {
|
||||||
const { setSubmitting, resetForm } = formArgs;
|
const { setSubmitting, resetForm } = formArgs;
|
||||||
const formValues = {
|
const formValues = {
|
||||||
...values,
|
...values,
|
||||||
active: parseBoolean(values.active, true),
|
active: parseBoolean(values.active, true),
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSuccess = (res) => {
|
const onSuccess = (res: { data?: unknown }) => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get(
|
message: intl.get(
|
||||||
isNewMode
|
isNewMode
|
||||||
@@ -83,60 +138,40 @@ function CustomerFormFormik({
|
|||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
saveInvoke(onSubmitError, values, formArgs, submitPayload);
|
saveInvoke(onSubmitError, values, formArgs, submitPayload);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isNewMode) {
|
if (isNewMode) {
|
||||||
createCustomerMutate(formValues).then(onSuccess).catch(onError);
|
createCustomerMutate(formValues).then(onSuccess).catch(onError);
|
||||||
} else {
|
} else {
|
||||||
editCustomerMutate([customer.id, formValues])
|
if (!customer) return;
|
||||||
.then(onSuccess)
|
editCustomerMutate([customer.id, formValues]).then(onSuccess).catch(onError);
|
||||||
.catch(onError);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Formik<CustomerFormValues>
|
||||||
className={classNames(
|
|
||||||
CLASSES.PAGE_FORM,
|
|
||||||
CLASSES.PAGE_FORM_CUSTOMER,
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Formik
|
|
||||||
validationSchema={isNewMode ? CreateCustomerForm : EditCustomerForm}
|
validationSchema={isNewMode ? CreateCustomerForm : EditCustomerForm}
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<CustomerFormHeaderPrimary>
|
<CustomerFormFields>
|
||||||
<CustomerFormPrimarySection />
|
<CustomerFormContent />
|
||||||
</CustomerFormHeaderPrimary>
|
</CustomerFormFields>
|
||||||
|
|
||||||
<div className={'page-form__after-priamry-section'}>
|
|
||||||
<CustomerFormAfterPrimarySection />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
|
||||||
<CustomersTabs />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CustomerFloatingActions onCancel={onCancel} />
|
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CustomerFormHeaderPrimary = styled.div`
|
const CustomerFormFields = styled.div`
|
||||||
--x-border: #e4e4e4;
|
.bp4-form-content,
|
||||||
|
.bp6-form-content {
|
||||||
.bp4-dark & {
|
min-width: 300px;
|
||||||
--x-border: var(--color-dark-gray3);
|
}
|
||||||
|
.bp4-form-group{
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.bp4-form-group.bp4-inline label.bp4-label {
|
||||||
|
min-width: 140px;
|
||||||
}
|
}
|
||||||
padding: 10px 0 0;
|
|
||||||
margin: 0 0 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
border-bottom: 1px solid var(--x-border);
|
|
||||||
max-width: 1000px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default compose(withCurrentOrganization())(CustomerFormFormik);
|
export const CustomerFormFormik = compose(withCurrentOrganization(undefined))(CustomerFormFormikRoot);
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Box, FFormGroup, FormattedMessage as T, FTextArea } from '@/components';
|
||||||
|
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
|
||||||
|
|
||||||
|
export function CustomerFormNotesSection() {
|
||||||
|
return (
|
||||||
|
<Box data-section-id="notes">
|
||||||
|
<CustomerFormSectionTitle>
|
||||||
|
<T id={'notes'} />
|
||||||
|
</CustomerFormSectionTitle>
|
||||||
|
|
||||||
|
<FFormGroup name={'note'} label={<T id={'note'} />} inline>
|
||||||
|
<FTextArea name={'note'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,73 +2,53 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useParams, useHistory } from 'react-router-dom';
|
import { useParams, useHistory } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Box, DashboardCard, DashboardInsider } from '@/components';
|
||||||
import { DashboardCard, DashboardInsider } from '@/components';
|
import { CustomerFormFormik, ustomerFormFormik } from './CustomerFormFormik';
|
||||||
|
|
||||||
import CustomerFormFormik from './CustomerFormFormik';
|
|
||||||
import {
|
import {
|
||||||
CustomerFormProvider,
|
CustomerFormProvider,
|
||||||
useCustomerFormContext,
|
useCustomerFormContext,
|
||||||
} from './CustomerFormProvider';
|
} from './CustomerFormProvider';
|
||||||
|
|
||||||
/**
|
|
||||||
* Customer form page loading.
|
|
||||||
* @returns {JSX}
|
|
||||||
*/
|
|
||||||
function CustomerFormPageLoading({ children }) {
|
|
||||||
const { isFormLoading } = useCustomerFormContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CustomerDashboardInsider loading={isFormLoading}>
|
|
||||||
{children}
|
|
||||||
</CustomerDashboardInsider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customer form page.
|
* Customer form page.
|
||||||
* @returns {JSX}
|
* @returns {JSX}
|
||||||
*/
|
*/
|
||||||
export default function CustomerFormPage() {
|
export default function CustomerFormPage() {
|
||||||
const history = useHistory();
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
const customerId = parseInt(id, 10);
|
const customerId = parseInt(id, 10);
|
||||||
|
|
||||||
// Handle the form submit success.
|
|
||||||
const handleSubmitSuccess = (values, formArgs, submitPayload) => {
|
|
||||||
if (!submitPayload.noRedirect) {
|
|
||||||
history.push('/customers');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Handle the form cancel button click.
|
|
||||||
const handleFormCancel = () => {
|
|
||||||
history.goBack();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomerFormProvider customerId={customerId}>
|
<CustomerFormProvider customerId={customerId}>
|
||||||
<CustomerFormPageLoading>
|
<CustomerFormPageContent />
|
||||||
<DashboardCard page>
|
|
||||||
<CustomerFormPageFormik
|
|
||||||
onSubmitSuccess={handleSubmitSuccess}
|
|
||||||
onCancel={handleFormCancel}
|
|
||||||
/>
|
|
||||||
</DashboardCard>
|
|
||||||
</CustomerFormPageLoading>
|
|
||||||
</CustomerFormProvider>
|
</CustomerFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomerFormPageFormik = styled(CustomerFormFormik)`
|
function CustomerFormPageContent() {
|
||||||
.page-form {
|
const history = useHistory();
|
||||||
&__floating-actions {
|
const { isFormLoading } = useCustomerFormContext();
|
||||||
margin-left: -40px;
|
|
||||||
margin-right: -40px;
|
|
||||||
|
const handleSubmitSuccess = (values, formArgs, submitPayload) => {
|
||||||
|
if (!submitPayload.noRedirect) {
|
||||||
|
history.push('/customers');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
|
||||||
|
|
||||||
const CustomerDashboardInsider = styled(DashboardInsider)`
|
// Handle the form cancel button click.
|
||||||
padding-bottom: 64px;
|
const handleFormCancel = () => {
|
||||||
`;
|
history.goBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider loading={isFormLoading}>
|
||||||
|
<Box mx={'auto'} maxWidth={800}>
|
||||||
|
<CustomerFormFormik
|
||||||
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
|
onCancel={handleFormCancel}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DashboardInsider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { FormGroup, InputGroup, ControlGroup } from '@blueprintjs/core';
|
|
||||||
import { FastField, Field, ErrorMessage } from 'formik';
|
|
||||||
import {
|
|
||||||
Hint,
|
|
||||||
FieldRequiredHint,
|
|
||||||
SalutationList,
|
|
||||||
DisplayNameList,
|
|
||||||
FormattedMessage as T,
|
|
||||||
FInputGroup,
|
|
||||||
FFormGroup,
|
|
||||||
} from '@/components';
|
|
||||||
import CustomerTypeRadioField from './CustomerTypeRadioField';
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
import { inputIntent } from '@/utils';
|
|
||||||
import { useAutofocus } from '@/hooks';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Customer form primary section.
|
|
||||||
*/
|
|
||||||
export default function CustomerFormPrimarySection({}) {
|
|
||||||
const firstNameFieldRef = useAutofocus();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={'customer-form__primary-section-content'}>
|
|
||||||
{/**-----------Customer type. -----------*/}
|
|
||||||
<CustomerTypeRadioField />
|
|
||||||
|
|
||||||
{/**----------- Contact name -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'salutation'}
|
|
||||||
label={<T id={'contact_name'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<ControlGroup>
|
|
||||||
<SalutationList
|
|
||||||
name={'salutation'}
|
|
||||||
popoverProps={{ minimal: true }}
|
|
||||||
/>
|
|
||||||
<FInputGroup
|
|
||||||
name={'first_name'}
|
|
||||||
placeholder={intl.get('first_name')}
|
|
||||||
inputRef={(ref) => (firstNameFieldRef.current = ref)}
|
|
||||||
/>
|
|
||||||
<FInputGroup name={'last_name'} placeholder={intl.get('last_name')} />
|
|
||||||
</ControlGroup>
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*----------- Company Name -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'company_name'}
|
|
||||||
label={<T id={'company_name'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'company_name'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*----------- Display Name -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'display_name'}
|
|
||||||
label={
|
|
||||||
<>
|
|
||||||
<T id={'display_name'} />
|
|
||||||
<FieldRequiredHint />
|
|
||||||
<Hint />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<DisplayNameList
|
|
||||||
name={'display_name'}
|
|
||||||
popoverProps={{ minimal: true }}
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
// @ts-nocheck
|
import React, { createContext, useState } from 'react';
|
||||||
import React, { useState, createContext } from 'react';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
useCustomer,
|
useCustomer,
|
||||||
@@ -12,10 +11,60 @@ import {
|
|||||||
import { Features } from '@/constants';
|
import { Features } from '@/constants';
|
||||||
import { useFeatureCan } from '@/hooks/state';
|
import { useFeatureCan } from '@/hooks/state';
|
||||||
|
|
||||||
const CustomerFormContext = createContext();
|
type CustomerFormSubmitPayload = {
|
||||||
|
noRedirect?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
function CustomerFormProvider({ query, customerId, ...props }) {
|
type Customer = {
|
||||||
const { state } = useLocation();
|
id: number;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Currency = {
|
||||||
|
currency_code: string;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Branch = {
|
||||||
|
id: number;
|
||||||
|
primary?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CustomerFormContextValue = {
|
||||||
|
customerId?: number;
|
||||||
|
customer?: Customer;
|
||||||
|
currencies: Currency[];
|
||||||
|
branches: Branch[];
|
||||||
|
contactDuplicate?: Customer;
|
||||||
|
submitPayload: CustomerFormSubmitPayload;
|
||||||
|
isNewMode: boolean;
|
||||||
|
|
||||||
|
isCustomerLoading: boolean;
|
||||||
|
isCurrenciesLoading: boolean;
|
||||||
|
isBranchesSuccess: boolean;
|
||||||
|
isFormLoading: boolean;
|
||||||
|
|
||||||
|
setSubmitPayload: React.Dispatch<
|
||||||
|
React.SetStateAction<CustomerFormSubmitPayload>
|
||||||
|
>;
|
||||||
|
|
||||||
|
editCustomerMutate: (args: [number, any]) => Promise<any>;
|
||||||
|
createCustomerMutate: (values: any) => Promise<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CustomerFormProviderProps = {
|
||||||
|
query?: unknown;
|
||||||
|
customerId?: number;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomerFormContext = createContext<CustomerFormContextValue | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
export function CustomerFormProvider({ query, customerId, children }: CustomerFormProviderProps) {
|
||||||
|
const { state } = useLocation<{ action?: number | string }>();
|
||||||
const contactId = state?.action;
|
const contactId = state?.action;
|
||||||
|
|
||||||
// Features guard.
|
// Features guard.
|
||||||
@@ -33,7 +82,7 @@ function CustomerFormProvider({ query, customerId, ...props }) {
|
|||||||
{ enabled: !!contactId },
|
{ enabled: !!contactId },
|
||||||
);
|
);
|
||||||
// Handle fetch Currencies data table
|
// Handle fetch Currencies data table
|
||||||
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
|
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies(undefined);
|
||||||
|
|
||||||
// Fetches the branches list.
|
// Fetches the branches list.
|
||||||
const {
|
const {
|
||||||
@@ -43,23 +92,26 @@ function CustomerFormProvider({ query, customerId, ...props }) {
|
|||||||
} = useBranches(query, { enabled: isBranchFeatureCan });
|
} = useBranches(query, { enabled: isBranchFeatureCan });
|
||||||
|
|
||||||
// Form submit payload.
|
// Form submit payload.
|
||||||
const [submitPayload, setSubmitPayload] = useState({});
|
const [submitPayload, setSubmitPayload] = useState<CustomerFormSubmitPayload>({});
|
||||||
|
|
||||||
const { mutateAsync: editCustomerMutate } = useEditCustomer();
|
const editCustomerMutation = useEditCustomer(undefined) as any;
|
||||||
const { mutateAsync: createCustomerMutate } = useCreateCustomer();
|
const createCustomerMutation = useCreateCustomer(undefined) as any;
|
||||||
|
const editCustomerMutate = editCustomerMutation.mutateAsync as CustomerFormContextValue['editCustomerMutate'];
|
||||||
|
const createCustomerMutate =
|
||||||
|
createCustomerMutation.mutateAsync as CustomerFormContextValue['createCustomerMutate'];
|
||||||
|
|
||||||
// determines whether the form new or duplicate mode.
|
// determines whether the form new or duplicate mode.
|
||||||
const isNewMode = contactId || !customerId;
|
const isNewMode = Boolean(contactId) || !customerId;
|
||||||
|
|
||||||
const isFormLoading =
|
const isFormLoading =
|
||||||
isCustomerLoading || isCurrenciesLoading || isBranchesLoading;
|
isCustomerLoading || isCurrenciesLoading || isBranchesLoading;
|
||||||
|
|
||||||
const provider = {
|
const provider: CustomerFormContextValue = {
|
||||||
customerId,
|
customerId,
|
||||||
customer,
|
customer: customer as Customer | undefined,
|
||||||
currencies,
|
currencies: (currencies as Currency[]) ?? [],
|
||||||
branches,
|
branches: (branches as Branch[]) ?? [],
|
||||||
contactDuplicate,
|
contactDuplicate: contactDuplicate as Customer | undefined,
|
||||||
submitPayload,
|
submitPayload,
|
||||||
isNewMode,
|
isNewMode,
|
||||||
|
|
||||||
@@ -73,9 +125,19 @@ function CustomerFormProvider({ query, customerId, ...props }) {
|
|||||||
createCustomerMutate,
|
createCustomerMutate,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <CustomerFormContext.Provider value={provider} {...props} />;
|
return (
|
||||||
|
<CustomerFormContext.Provider value={provider}>
|
||||||
|
{children}
|
||||||
|
</CustomerFormContext.Provider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const useCustomerFormContext = () => React.useContext(CustomerFormContext);
|
export const useCustomerFormContext = () => {
|
||||||
|
const ctx = React.useContext(CustomerFormContext);
|
||||||
export { CustomerFormProvider, useCustomerFormContext };
|
if (!ctx) {
|
||||||
|
throw new Error(
|
||||||
|
'useCustomerFormContext must be used within a CustomerFormProvider',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
const customerFormSectionTitleClass = css`
|
||||||
|
font-size: 14px;
|
||||||
|
color: #8f99a8;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
margin-top: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function CustomerFormSectionTitle({ children }: { children: React.ReactNode | string }) {
|
||||||
|
return <h4 className={customerFormSectionTitleClass}>{children}</h4>;
|
||||||
|
}
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Classes } from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T, FFormGroup, FTextArea } from '@/components';
|
import { FormattedMessage as T, FFormGroup, FTextArea } from '@/components';
|
||||||
|
|
||||||
export default function CustomerNotePanel({ errors, touched, getFieldProps }) {
|
export default function CustomerNotePanel({ errors, touched, getFieldProps }) {
|
||||||
return (
|
return (
|
||||||
<div className={'tab-panel--note'}>
|
<FFormGroup name={'note'} label={<T id={'note'} />} inline={false} fill>
|
||||||
<FFormGroup name={'note'} label={<T id={'note'} />} inline={false}>
|
<FTextArea name={'note'} fill />
|
||||||
<FTextArea name={'note'} />
|
</FFormGroup>
|
||||||
</FFormGroup>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@/components';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
FFormGroup,
|
||||||
|
FInputGroup,
|
||||||
|
FTextArea,
|
||||||
|
} from '@/components';
|
||||||
|
import { CustomerFormSectionTitle } from './CustomerFormSectionTitle';
|
||||||
|
|
||||||
|
export function CustomerShippingAddress() {
|
||||||
|
return (
|
||||||
|
<Box data-section-id="shippingAddress">
|
||||||
|
<CustomerFormSectionTitle>
|
||||||
|
<T id={'shipping_address'} />
|
||||||
|
</CustomerFormSectionTitle>
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address_country'}
|
||||||
|
label={<T id={'country'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'shipping_address_country'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address1'}
|
||||||
|
label={<T id={'address_line_1'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FTextArea name={'shipping_address1'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address2'}
|
||||||
|
label={<T id={'address_line_2'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FTextArea name={'shipping_address2'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address_city'}
|
||||||
|
label={<T id={'city_town'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'shipping_address_city'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address_state'}
|
||||||
|
label={<T id={'state'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'shipping_address_state'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address_postcode'}
|
||||||
|
label={<T id={'zip_code'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'shipping_address_postcode'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address_phone'}
|
||||||
|
label={<T id={'phone'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<FInputGroup name={'shipping_address_phone'} fill />
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,27 +1,52 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import classNames from 'classnames';
|
import { Button, ButtonGroup } from '@blueprintjs/core';
|
||||||
import { Radio } from '@blueprintjs/core';
|
import { FastField } from 'formik';
|
||||||
import { FormattedMessage as T, FFormGroup, FRadioGroup } from '@/components';
|
import { FormattedMessage as T, FFormGroup } from '@/components';
|
||||||
|
|
||||||
import { handleStringChange, saveInvoke } from '@/utils';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customer type radio field.
|
* Customer type selector (button group).
|
||||||
*/
|
*/
|
||||||
export default function RadioCustomer() {
|
export function CustomerTypeRadioField() {
|
||||||
return (
|
return (
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'customer_type'}
|
name={'customer_type'}
|
||||||
label={<T id={'customer_type'} />}
|
label={<T id={'customer_type'} />}
|
||||||
inline
|
inline
|
||||||
|
fill
|
||||||
fastField
|
fastField
|
||||||
>
|
>
|
||||||
<FRadioGroup name={'customer_type'} inline>
|
<FastField name="customer_type">
|
||||||
<Radio label={intl.get('business')} value="business" />
|
{({ field, form }) => (
|
||||||
<Radio label={intl.get('individual')} value="individual" />
|
<ButtonGroup>
|
||||||
</FRadioGroup>
|
<Button
|
||||||
|
type="button"
|
||||||
|
outlined
|
||||||
|
small
|
||||||
|
active={field.value === 'business'}
|
||||||
|
onClick={() => {
|
||||||
|
form.setFieldValue('customer_type', 'business');
|
||||||
|
form.setFieldTouched('customer_type', true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{intl.get('business')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
outlined
|
||||||
|
small
|
||||||
|
active={field.value === 'individual'}
|
||||||
|
onClick={() => {
|
||||||
|
form.setFieldValue('customer_type', 'individual');
|
||||||
|
form.setFieldTouched('customer_type', true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{intl.get('individual')}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Tabs, Tab } from '@blueprintjs/core';
|
|||||||
|
|
||||||
import CustomerAddressTabs from './CustomerAddressTabs';
|
import CustomerAddressTabs from './CustomerAddressTabs';
|
||||||
import CustomerAttachmentTabs from './CustomerAttachmentTabs';
|
import CustomerAttachmentTabs from './CustomerAttachmentTabs';
|
||||||
import CustomerFinancialPanel from './CustomerFinancialPanel';
|
import CustomerFinancialPanel from './CustomerFormFinancialSection';
|
||||||
import CustomerNotePanel from './CustomerNotePanel';
|
import CustomerNotePanel from './CustomerNotePanel';
|
||||||
|
|
||||||
export default function CustomersTabs() {
|
export default function CustomersTabs() {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const defaultInitialValues = {
|
|||||||
last_name: '',
|
last_name: '',
|
||||||
company_name: '',
|
company_name: '',
|
||||||
display_name: '',
|
display_name: '',
|
||||||
|
code: '',
|
||||||
|
|
||||||
email: '',
|
email: '',
|
||||||
work_phone: '',
|
work_phone: '',
|
||||||
|
|||||||
+1
-23
@@ -8,9 +8,7 @@ import {
|
|||||||
CustomerFormProvider,
|
CustomerFormProvider,
|
||||||
useCustomerFormContext,
|
useCustomerFormContext,
|
||||||
} from '@/containers/Customers/CustomerForm/CustomerFormProvider';
|
} from '@/containers/Customers/CustomerForm/CustomerFormProvider';
|
||||||
import CustomerFormFormik, {
|
import { CustomerFormFormik } from '@/containers/Customers/CustomerForm/CustomerFormFormik';
|
||||||
CustomerFormHeaderPrimary,
|
|
||||||
} from '@/containers/Customers/CustomerForm/CustomerFormFormik';
|
|
||||||
|
|
||||||
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
|
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
@@ -55,34 +53,14 @@ function QuickCustomerFormDrawer({
|
|||||||
return (
|
return (
|
||||||
<CustomerFormProvider customerId={customerId}>
|
<CustomerFormProvider customerId={customerId}>
|
||||||
<DrawerCustomerFormLoading>
|
<DrawerCustomerFormLoading>
|
||||||
<CustomerFormCard>
|
|
||||||
<CustomerFormFormik
|
<CustomerFormFormik
|
||||||
initialValues={{ first_name: displayName }}
|
initialValues={{ first_name: displayName }}
|
||||||
onSubmitSuccess={handleSubmitSuccess}
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
onCancel={handleCancelForm}
|
onCancel={handleCancelForm}
|
||||||
/>
|
/>
|
||||||
</CustomerFormCard>
|
|
||||||
</DrawerCustomerFormLoading>
|
</DrawerCustomerFormLoading>
|
||||||
</CustomerFormProvider>
|
</CustomerFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default R.compose(withDrawerActions)(QuickCustomerFormDrawer);
|
export default R.compose(withDrawerActions)(QuickCustomerFormDrawer);
|
||||||
|
|
||||||
const CustomerFormCard = styled(Card)`
|
|
||||||
margin: 15px;
|
|
||||||
padding: 25px;
|
|
||||||
margin-bottom: calc(15px + 65px);
|
|
||||||
|
|
||||||
${CustomerFormHeaderPrimary} {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
.page-form {
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&__floating-actions {
|
|
||||||
margin-left: -41px;
|
|
||||||
margin-right: -41px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|||||||
+2
-21
@@ -8,8 +8,8 @@ import {
|
|||||||
VendorFormProvider,
|
VendorFormProvider,
|
||||||
useVendorFormContext,
|
useVendorFormContext,
|
||||||
} from '@/containers/Vendors/VendorForm/VendorFormProvider';
|
} from '@/containers/Vendors/VendorForm/VendorFormProvider';
|
||||||
import VendorFormFormik, {
|
import {
|
||||||
VendorFormHeaderPrimary,
|
VendorFormFormik,
|
||||||
} from '@/containers/Vendors/VendorForm/VendorFormFormik';
|
} from '@/containers/Vendors/VendorForm/VendorFormFormik';
|
||||||
|
|
||||||
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
|
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
|
||||||
@@ -62,13 +62,11 @@ function QuickVendorFormDrawer({
|
|||||||
return (
|
return (
|
||||||
<VendorFormProvider vendorId={vendorId}>
|
<VendorFormProvider vendorId={vendorId}>
|
||||||
<DrawerVendorFormLoading>
|
<DrawerVendorFormLoading>
|
||||||
<VendorFormCard>
|
|
||||||
<VendorFormFormik
|
<VendorFormFormik
|
||||||
initialValues={{ first_name: displayName }}
|
initialValues={{ first_name: displayName }}
|
||||||
onSubmitSuccess={handleSubmitSuccess}
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
onCancel={handleCancelForm}
|
onCancel={handleCancelForm}
|
||||||
/>
|
/>
|
||||||
</VendorFormCard>
|
|
||||||
</DrawerVendorFormLoading>
|
</DrawerVendorFormLoading>
|
||||||
</VendorFormProvider>
|
</VendorFormProvider>
|
||||||
);
|
);
|
||||||
@@ -79,20 +77,3 @@ export default R.compose(
|
|||||||
withDashboardActions,
|
withDashboardActions,
|
||||||
)(QuickVendorFormDrawer);
|
)(QuickVendorFormDrawer);
|
||||||
|
|
||||||
const VendorFormCard = styled(Card)`
|
|
||||||
margin: 15px;
|
|
||||||
padding: 25px;
|
|
||||||
margin-bottom: calc(15px + 65px);
|
|
||||||
|
|
||||||
${VendorFormHeaderPrimary} {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
.page-form {
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&__floating-actions {
|
|
||||||
margin-left: -41px;
|
|
||||||
margin-right: -41px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|||||||
-1
@@ -18,7 +18,6 @@ export default function QuickWriteVendorDrawerContent({ displayName, autofillRef
|
|||||||
<DrawerHeaderContent
|
<DrawerHeaderContent
|
||||||
name={DRAWERS.QUICK_CREATE_CUSTOMER}
|
name={DRAWERS.QUICK_CREATE_CUSTOMER}
|
||||||
title={<T id={'create_a_new_vendor'} />}
|
title={<T id={'create_a_new_vendor'} />}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<DrawerBody>
|
<DrawerBody>
|
||||||
<QuickVendorFormDrawer displayName={displayName} autofillRef={autofillRef} />
|
<QuickVendorFormDrawer displayName={displayName} autofillRef={autofillRef} />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Dragzone, FormattedMessage as T } from '@/components';
|
|||||||
/**
|
/**
|
||||||
* Vendor Attachment Tab.
|
* Vendor Attachment Tab.
|
||||||
*/
|
*/
|
||||||
function VendorAttachmentTab() {
|
export function VendorAttachmentTab() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Dragzone
|
<Dragzone
|
||||||
@@ -17,5 +17,3 @@ function VendorAttachmentTab() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VendorAttachmentTab;
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { Box } from '@/components';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
FFormGroup,
|
||||||
|
FInputGroup,
|
||||||
|
FTextArea,
|
||||||
|
} from '@/components';
|
||||||
|
import { VendorFormSectionTitle } from './VendorFormSectionTitle';
|
||||||
|
|
||||||
|
export function VendorBillingAddress() {
|
||||||
|
return (
|
||||||
|
<Box data-section-id="billingAddress">
|
||||||
|
<VendorFormSectionTitle>
|
||||||
|
<T id={'billing_address'} />
|
||||||
|
</VendorFormSectionTitle>
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address_country'}
|
||||||
|
label={<T id={'country'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'billing_address_country'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address1'}
|
||||||
|
label={<T id={'address_line_1'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FTextArea name={'billing_address1'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address2'}
|
||||||
|
label={<T id={'address_line_2'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FTextArea name={'billing_address2'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address_city'}
|
||||||
|
label={<T id={'city_town'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'billing_address_city'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address_state'}
|
||||||
|
label={<T id={'state'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'billing_address_state'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address_postcode'}
|
||||||
|
label={<T id={'zip_code'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'billing_address_postcode'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'billing_address_phone'}
|
||||||
|
label={<T id={'phone'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'billing_address_phone'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ import { useCurrentOrganization } from '@/hooks/state';
|
|||||||
/**
|
/**
|
||||||
* Vendor Finaniceal Panel Tab.
|
* Vendor Finaniceal Panel Tab.
|
||||||
*/
|
*/
|
||||||
export default function VendorFinanicalPanelTab() {
|
export function VendorFinanicalPanelTab() {
|
||||||
const { currencies, branches } = useVendorFormContext();
|
const { currencies, branches } = useVendorFormContext();
|
||||||
|
|
||||||
// Sets the primary branch to form.
|
// Sets the primary branch to form.
|
||||||
@@ -44,10 +44,12 @@ export default function VendorFinanicalPanelTab() {
|
|||||||
label={<T id={'currency'} />}
|
label={<T id={'currency'} />}
|
||||||
fastField
|
fastField
|
||||||
inline
|
inline
|
||||||
|
fastField
|
||||||
>
|
>
|
||||||
<CurrencySelectList
|
<CurrencySelectList
|
||||||
name="currency_code"
|
name="currency_code"
|
||||||
items={currencies}
|
items={currencies}
|
||||||
|
fastField
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
@@ -93,16 +95,17 @@ function VendorOpeningBalanceField() {
|
|||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'opening_balance'}
|
name={'opening_balance'}
|
||||||
label={<T id={'opening_balance'} />}
|
label={<T id={'opening_balance'} />}
|
||||||
inline={true}
|
|
||||||
shouldUpdate={openingBalanceFieldShouldUpdate}
|
shouldUpdate={openingBalanceFieldShouldUpdate}
|
||||||
shouldUpdateDeps={{ currencyCode: values.currency_code }}
|
shouldUpdateDeps={{ currencyCode: values.currency_code }}
|
||||||
fastField={true}
|
inline
|
||||||
|
fastField
|
||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<InputPrependText text={values.currency_code} />
|
<InputPrependText text={values.currency_code} />
|
||||||
<FMoneyInputGroup
|
<FMoneyInputGroup
|
||||||
name={'opening_balance'}
|
name={'opening_balance'}
|
||||||
inputGroupProps={{ fill: true }}
|
inputGroupProps={{ fill: true }}
|
||||||
|
fastField
|
||||||
/>
|
/>
|
||||||
</ControlGroup>
|
</ControlGroup>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
@@ -123,8 +126,9 @@ function VendorOpeningBalanceAtField() {
|
|||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'opening_balance_at'}
|
name={'opening_balance_at'}
|
||||||
label={<T id={'opening_balance_at'} />}
|
label={<T id={'opening_balance_at'} />}
|
||||||
inline={true}
|
|
||||||
helperText={<ErrorMessage name="opening_balance_at" />}
|
helperText={<ErrorMessage name="opening_balance_at" />}
|
||||||
|
inline
|
||||||
|
fastField
|
||||||
>
|
>
|
||||||
<FDateInput
|
<FDateInput
|
||||||
name={'opening_balance_at'}
|
name={'opening_balance_at'}
|
||||||
@@ -132,7 +136,8 @@ function VendorOpeningBalanceAtField() {
|
|||||||
disabled={vendorId}
|
disabled={vendorId}
|
||||||
formatDate={(date) => date.toLocaleDateString()}
|
formatDate={(date) => date.toLocaleDateString()}
|
||||||
parseDate={(str) => new Date(str)}
|
parseDate={(str) => new Date(str)}
|
||||||
fill={true}
|
fill
|
||||||
|
fastField
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
);
|
);
|
||||||
@@ -156,12 +161,14 @@ function VendorOpeningBalanceExchangeRateField() {
|
|||||||
<FFormGroup
|
<FFormGroup
|
||||||
label={' '}
|
label={' '}
|
||||||
name={'opening_balance_exchange_rate'}
|
name={'opening_balance_exchange_rate'}
|
||||||
inline={true}
|
inline
|
||||||
|
fastField
|
||||||
>
|
>
|
||||||
<ExchangeRateInputGroup
|
<ExchangeRateInputGroup
|
||||||
fromCurrency={values.currency_code}
|
fromCurrency={values.currency_code}
|
||||||
toCurrency={currentOrganization.base_currency}
|
toCurrency={currentOrganization.base_currency}
|
||||||
name={'opening_balance_exchange_rate'}
|
name={'opening_balance_exchange_rate'}
|
||||||
|
fastField
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
|
||||||
import {
|
import {
|
||||||
Intent,
|
Intent,
|
||||||
Button,
|
Button,
|
||||||
@@ -11,53 +10,37 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
import { Group, Icon, FormattedMessage as T } from '@/components';
|
import { Group, Icon, FormattedMessage as T } from '@/components';
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
import { useVendorFormContext } from './VendorFormProvider';
|
import { useVendorFormContext } from './VendorFormProvider';
|
||||||
import { safeInvoke } from '@/utils';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor floating actions bar.
|
* Vendor floating actions bar.
|
||||||
*/
|
*/
|
||||||
export default function VendorFloatingActions({ onCancel }) {
|
export function VendorFloatingActions() {
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { resetForm, isSubmitting, submitForm } = useFormikContext();
|
const { isSubmitting, submitForm } = useFormikContext();
|
||||||
|
|
||||||
// Vendor form context.
|
// Vendor form context.
|
||||||
const { isNewMode, setSubmitPayload } = useVendorFormContext();
|
const { isNewMode, setSubmitPayload } = useVendorFormContext();
|
||||||
|
|
||||||
// Handle the submit button.
|
// Handle the submit button.
|
||||||
const handleSubmitBtnClick = (event) => {
|
const handleSubmitBtnClick = () => {
|
||||||
setSubmitPayload({ noRedirect: false });
|
setSubmitPayload({ noRedirect: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle the submit & new button click.
|
// Handle the submit & new button click.
|
||||||
const handleSubmitAndNewClick = (event) => {
|
const handleSubmitAndNewClick = () => {
|
||||||
submitForm();
|
submitForm();
|
||||||
setSubmitPayload({ noRedirect: true });
|
setSubmitPayload({ noRedirect: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle cancel button click.
|
|
||||||
const handleCancelBtnClick = (event) => {
|
|
||||||
safeInvoke(onCancel, event);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle clear button click.
|
|
||||||
const handleClearBtnClick = (event) => {
|
|
||||||
resetForm();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group
|
<FloatingActionsGroup spacing={10}>
|
||||||
spacing={10}
|
|
||||||
className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}
|
|
||||||
>
|
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{/* ----------- Save and New ----------- */}
|
{/* ----------- Save and New ----------- */}
|
||||||
<SaveButton
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
@@ -74,9 +57,9 @@ export default function VendorFloatingActions({ onCancel }) {
|
|||||||
/>
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
}
|
}
|
||||||
minimal={true}
|
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
position={Position.BOTTOM_LEFT}
|
position={Position.BOTTOM_RIGHT}
|
||||||
|
minimal
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
@@ -85,24 +68,16 @@ export default function VendorFloatingActions({ onCancel }) {
|
|||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
{/* ----------- Clear & Reset----------- */}
|
</FloatingActionsGroup>
|
||||||
<Button
|
|
||||||
className={'ml1'}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
onClick={handleClearBtnClick}
|
|
||||||
text={!isNewMode ? <T id={'reset'} /> : <T id={'clear'} />}
|
|
||||||
/>
|
|
||||||
{/* ----------- Cancel ----------- */}
|
|
||||||
<Button
|
|
||||||
className={'ml1'}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
onClick={handleCancelBtnClick}
|
|
||||||
text={<T id={'cancel'} />}
|
|
||||||
/>
|
|
||||||
</Group>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SaveButton = styled(Button)`
|
const FloatingActionsGroup = styled(Group)`
|
||||||
min-width: 100px;
|
padding: 10px 0;
|
||||||
`;
|
padding-left: 165px;
|
||||||
|
border-top: 1px solid #50555a;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: var(--color-card-background);
|
||||||
|
z-index: 1;
|
||||||
|
`;
|
||||||
+13
-12
@@ -2,21 +2,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { ControlGroup } from '@blueprintjs/core';
|
import { ControlGroup } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T, FFormGroup, FInputGroup } from '@/components';
|
import { FormattedMessage as T, FFormGroup, FInputGroup, Box } from '@/components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor form after primary section.
|
* Vendor form after primary section.
|
||||||
*/
|
*/
|
||||||
function VendorFormAfterPrimarySection() {
|
export function VendorFormAfterPrimarySection() {
|
||||||
return (
|
return (
|
||||||
<div className={'customer-form__after-primary-section-content'}>
|
<Box>
|
||||||
{/*------------ Vendor email -----------*/}
|
{/*------------ Vendor email -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'email'}
|
name={'email'}
|
||||||
label={<T id={'vendor_email'} />}
|
label={<T id={'vendor_email'} />}
|
||||||
inline={true}
|
inline
|
||||||
|
fastField
|
||||||
>
|
>
|
||||||
<FInputGroup name={'email'} />
|
<FInputGroup name={'email'} fastField />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
{/*------------ Phone number -----------*/}
|
{/*------------ Phone number -----------*/}
|
||||||
@@ -24,23 +25,23 @@ function VendorFormAfterPrimarySection() {
|
|||||||
name={'work_phone'}
|
name={'work_phone'}
|
||||||
className={'form-group--phone-number'}
|
className={'form-group--phone-number'}
|
||||||
label={<T id={'phone_number'} />}
|
label={<T id={'phone_number'} />}
|
||||||
inline={true}
|
inline
|
||||||
|
fastField
|
||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<FInputGroup name={'work_phone'} placeholder={intl.get('work')} />
|
<FInputGroup name={'work_phone'} placeholder={intl.get('work')} fastField />
|
||||||
<FInputGroup
|
<FInputGroup
|
||||||
name={'personal_phone'}
|
name={'personal_phone'}
|
||||||
placeholder={intl.get('mobile')}
|
placeholder={intl.get('mobile')}
|
||||||
|
fastField
|
||||||
/>
|
/>
|
||||||
</ControlGroup>
|
</ControlGroup>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
{/*------------ Vendor website -----------*/}
|
{/*------------ Vendor website -----------*/}
|
||||||
<FFormGroup name={'website'} label={<T id={'website'} />} inline={true}>
|
<FFormGroup name={'website'} label={<T id={'website'} />} inline fastField>
|
||||||
<FInputGroup name={'website'} placeholder={'http://'} />
|
<FInputGroup name={'website'} placeholder={'http://'} fastField />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VendorFormAfterPrimarySection;
|
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { ControlGroup, Divider, Icon as BlueprintIcon } from '@blueprintjs/core';
|
||||||
|
import {
|
||||||
|
Hint,
|
||||||
|
FieldRequiredHint,
|
||||||
|
SalutationList,
|
||||||
|
DisplayNameList,
|
||||||
|
FormattedMessage as T,
|
||||||
|
FInputGroup,
|
||||||
|
FFormGroup,
|
||||||
|
Box,
|
||||||
|
Icon,
|
||||||
|
Stack,
|
||||||
|
} from '@/components';
|
||||||
|
import { VendorFormSectionTitle } from './VendorFormSectionTitle';
|
||||||
|
import { useAutofocus } from '@/hooks';
|
||||||
|
|
||||||
|
export function VendorFormBasicSection({}) {
|
||||||
|
const firstNameFieldRef = useAutofocus();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box data-section-id="primary">
|
||||||
|
<VendorFormSectionTitle>Vendor details</VendorFormSectionTitle>
|
||||||
|
|
||||||
|
{/**----------- Contact name -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'salutation'}
|
||||||
|
label={<T id={'contact_name'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<ControlGroup fill>
|
||||||
|
<SalutationList
|
||||||
|
name={'salutation'}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
<FInputGroup
|
||||||
|
name={'first_name'}
|
||||||
|
placeholder={intl.get('first_name')}
|
||||||
|
inputRef={(ref) => (firstNameFieldRef.current = ref)}
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
<FInputGroup
|
||||||
|
name={'last_name'}
|
||||||
|
placeholder={intl.get('last_name')}
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'code'}
|
||||||
|
label={'Vendor Code'}
|
||||||
|
helperText="Add a unique account number to identify, reference and search for the contact."
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'code'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*----------- Company Name -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'company_name'}
|
||||||
|
label={<T id={'company_name'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'company_name'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*----------- Display Name -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'display_name'}
|
||||||
|
label={<T id={'display_name'} />}
|
||||||
|
helperText="This is the name that appears on invoices and emails."
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<DisplayNameList
|
||||||
|
name={'display_name'}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
buttonProps={{ fill: true }}
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<Divider style={{ margin: '20px 0' }} />
|
||||||
|
|
||||||
|
{/*------------ Vendor email -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'email'}
|
||||||
|
label={<T id={'vendor_email'} />}
|
||||||
|
inline
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup
|
||||||
|
name={'email'}
|
||||||
|
leftIcon={<Icon icon="envelope" />}
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------ Phone number -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'work_phone'}
|
||||||
|
className={'form-group--phone-number'}
|
||||||
|
label={<T id={'phone_number'} />}
|
||||||
|
inline
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<Stack spacing={10}>
|
||||||
|
<FInputGroup name={'work_phone'} placeholder={intl.get('work')} leftIcon="phone" fastField
|
||||||
|
/>
|
||||||
|
<FInputGroup
|
||||||
|
name={'personal_phone'}
|
||||||
|
placeholder={intl.get('mobile')}
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------ Vendor website -----------*/}
|
||||||
|
<FFormGroup name={'website'} label={<T id={'website'} />} inline fastField>
|
||||||
|
<FInputGroup
|
||||||
|
name={'website'}
|
||||||
|
placeholder={'http://'}
|
||||||
|
leftIcon={<BlueprintIcon icon="globe-network" />}
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { Tab } from "@blueprintjs/core";
|
||||||
|
import { Card, Group } from "@/components";
|
||||||
|
import { Tabs } from "@blueprintjs/core";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { VendorFloatingActions } from "./VendorFloatingActions";
|
||||||
|
import { VendorFormSections } from "./VendorFormFields";
|
||||||
|
|
||||||
|
export function VendorFormContent() {
|
||||||
|
const [selectedTabId, setSelectedTabId] = useState('primary');
|
||||||
|
|
||||||
|
const handleTabChange = (tabId: string) => {
|
||||||
|
const sectionId = String(tabId);
|
||||||
|
setSelectedTabId(sectionId);
|
||||||
|
|
||||||
|
const section = document.querySelector(
|
||||||
|
`[data-section-id="${sectionId}"]`,
|
||||||
|
);
|
||||||
|
if (section) {
|
||||||
|
section.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={css`padding-bottom: 0 !important;`}>
|
||||||
|
<Group verticalAlign={'top'} alignItems={'flex-start'} flexWrap={'nowrap'}>
|
||||||
|
<Tabs
|
||||||
|
selectedTabId={selectedTabId}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
className={css`position: sticky; top: 20px;`}
|
||||||
|
vertical
|
||||||
|
>
|
||||||
|
<Tab id={'primary'} title={'Basic'} />
|
||||||
|
<Tab id={'financial'} title={'Financial'} />
|
||||||
|
<Tab id={'billingAddress'} title={'Billing address'} />
|
||||||
|
<Tab id={'shippingAddress'} title={'Shipping address'} />
|
||||||
|
<Tab id={'notes'} title={'Notes'} />
|
||||||
|
</Tabs>
|
||||||
|
<VendorFormSections />
|
||||||
|
</Group>
|
||||||
|
<VendorFloatingActions />
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { Divider } from '@blueprintjs/core';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { Box } from '@/components';
|
||||||
|
|
||||||
|
import { VendorFormBasicSection } from './VendorFormBasicSection';
|
||||||
|
import { VendorFormFinancialSection } from './VendorFormFinancialSection';
|
||||||
|
import { VendorBillingAddress } from './VendorBillingAddress';
|
||||||
|
import { VendorShippingAddress } from './VendorShippingAddress';
|
||||||
|
import { VendorFormNotesSection } from './VendorFormNotesSection';
|
||||||
|
|
||||||
|
const vendorFormSectionDividerClass = css`
|
||||||
|
margin: 20px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function VendorFormSections() {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<VendorFormBasicSection />
|
||||||
|
<Divider className={vendorFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<VendorFormFinancialSection />
|
||||||
|
<Divider className={vendorFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<VendorBillingAddress />
|
||||||
|
<Divider className={vendorFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<VendorShippingAddress />
|
||||||
|
<Divider className={vendorFormSectionDividerClass} />
|
||||||
|
|
||||||
|
<VendorFormNotesSection />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { FormGroup, Position, ControlGroup } from '@blueprintjs/core';
|
||||||
|
import { ErrorMessage, useFormikContext } from 'formik';
|
||||||
|
import { Features } from '@/constants';
|
||||||
|
import {
|
||||||
|
FFormGroup,
|
||||||
|
FormattedMessage as T,
|
||||||
|
InputPrependText,
|
||||||
|
CurrencySelectList,
|
||||||
|
BranchSelect,
|
||||||
|
FeatureCan,
|
||||||
|
FMoneyInputGroup,
|
||||||
|
ExchangeRateInputGroup,
|
||||||
|
FDateInput,
|
||||||
|
Icon,
|
||||||
|
Box,
|
||||||
|
} from '@/components';
|
||||||
|
import { useVendorFormContext } from './VendorFormProvider';
|
||||||
|
import {
|
||||||
|
openingBalanceFieldShouldUpdate,
|
||||||
|
useIsVendorForeignCurrency,
|
||||||
|
useSetPrimaryBranchToForm,
|
||||||
|
} from './utils';
|
||||||
|
import { useCurrentOrganization } from '@/hooks/state';
|
||||||
|
import { VendorFormSectionTitle } from './VendorFormSectionTitle';
|
||||||
|
|
||||||
|
export function VendorFormFinancialSection() {
|
||||||
|
const { currencies, vendorId, branches } = useVendorFormContext();
|
||||||
|
|
||||||
|
// Sets the primary branch to form.
|
||||||
|
useSetPrimaryBranchToForm();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box data-section-id="financial">
|
||||||
|
<VendorFormSectionTitle>
|
||||||
|
<T id={'financial_details'} />
|
||||||
|
</VendorFormSectionTitle>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'currency_code'}
|
||||||
|
label={<T id={'currency'} />}
|
||||||
|
fastField
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<CurrencySelectList
|
||||||
|
name="currency_code"
|
||||||
|
items={currencies}
|
||||||
|
disabled={vendorId}
|
||||||
|
fastField
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<VendorOpeningBalanceField />
|
||||||
|
<VendorOpeningBalanceExchangeRateField />
|
||||||
|
<VendorOpeningBalanceAtField />
|
||||||
|
|
||||||
|
<FeatureCan feature={Features.Branches}>
|
||||||
|
<FFormGroup
|
||||||
|
label={<T id={'vendor.label.opening_branch'} />}
|
||||||
|
name={'opening_balance_branch_id'}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<BranchSelect
|
||||||
|
name={'opening_balance_branch_id'}
|
||||||
|
branches={branches}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
</FeatureCan>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor opening balance at date field.
|
||||||
|
* @returns {JSX.Element}
|
||||||
|
*/
|
||||||
|
function VendorOpeningBalanceAtField() {
|
||||||
|
const { vendorId } = useVendorFormContext();
|
||||||
|
|
||||||
|
// Cannot continue if the vendor id is defined.
|
||||||
|
if (vendorId) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FFormGroup
|
||||||
|
name={'opening_balance_at'}
|
||||||
|
label={<T id={'opening_balance_at'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
helperText={<ErrorMessage name="opening_balance_at" />}
|
||||||
|
>
|
||||||
|
<FDateInput
|
||||||
|
name={'opening_balance_at'}
|
||||||
|
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||||
|
disabled={vendorId}
|
||||||
|
formatDate={(date) => date.toLocaleDateString()}
|
||||||
|
parseDate={(str) => new Date(str)}
|
||||||
|
inputProps={{
|
||||||
|
leftIcon: <Icon icon={'date-range'} />,
|
||||||
|
}}
|
||||||
|
fill={true}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function VendorOpeningBalanceField() {
|
||||||
|
const { vendorId } = useVendorFormContext();
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
|
// Cannot continue if the vendor id is defined.
|
||||||
|
if (vendorId) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FFormGroup
|
||||||
|
label={<T id={'opening_balance'} />}
|
||||||
|
name={'opening_balance'}
|
||||||
|
inline
|
||||||
|
shouldUpdate={openingBalanceFieldShouldUpdate}
|
||||||
|
shouldUpdateDeps={{ currencyCode: values.currency_code }}
|
||||||
|
fastField={true}
|
||||||
|
fill
|
||||||
|
>
|
||||||
|
<ControlGroup fill>
|
||||||
|
<InputPrependText text={values.currency_code as string} />
|
||||||
|
<FMoneyInputGroup
|
||||||
|
name={'opening_balance'}
|
||||||
|
fastField
|
||||||
|
inputGroupProps={{ fill: true }}
|
||||||
|
/>
|
||||||
|
</ControlGroup>
|
||||||
|
</FFormGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function VendorOpeningBalanceExchangeRateField() {
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
const { vendorId } = useVendorFormContext();
|
||||||
|
const currentOrganization = useCurrentOrganization();
|
||||||
|
|
||||||
|
const isForeignVendor = useIsVendorForeignCurrency();
|
||||||
|
|
||||||
|
// Can't continue if the vendor is not foreign.
|
||||||
|
if (!isForeignVendor || vendorId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ExchangeRateInputGroup
|
||||||
|
fromCurrency={values.currency_code}
|
||||||
|
toCurrency={currentOrganization.base_currency}
|
||||||
|
name={'opening_balance_exchange_rate'}
|
||||||
|
onRecalcConfirm={() => {}}
|
||||||
|
onCancel={() => {}}
|
||||||
|
formGroupProps={{ label: ' ' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
@@ -7,16 +7,13 @@ import classNames from 'classnames';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster, Box } from '@/components';
|
||||||
import {
|
import {
|
||||||
CreateVendorFormSchema,
|
CreateVendorFormSchema,
|
||||||
EditVendorFormSchema,
|
EditVendorFormSchema,
|
||||||
} from './VendorForm.schema';
|
} from './VendorForm.schema';
|
||||||
|
|
||||||
import VendorTabs from './VendorsTabs';
|
import { VendorFormContent } from './VendorFormContent';
|
||||||
import VendorFormPrimarySection from './VendorFormPrimarySection';
|
|
||||||
import VendorFormAfterPrimarySection from './VendorFormAfterPrimarySection';
|
|
||||||
import VendorFloatingActions from './VendorFloatingActions';
|
|
||||||
|
|
||||||
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
import { withCurrentOrganization } from '@/containers/Organization/withCurrentOrganization';
|
||||||
|
|
||||||
@@ -24,12 +21,10 @@ import { useVendorFormContext } from './VendorFormProvider';
|
|||||||
import { compose, transformToForm, safeInvoke, parseBoolean } from '@/utils';
|
import { compose, transformToForm, safeInvoke, parseBoolean } from '@/utils';
|
||||||
import { defaultInitialValues } from './utils';
|
import { defaultInitialValues } from './utils';
|
||||||
|
|
||||||
import '@/style/pages/Vendors/Form.scss';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor form.
|
* Vendor form.
|
||||||
*/
|
*/
|
||||||
function VendorFormFormik({
|
function VendorFormFormikBase({
|
||||||
// #withCurrentOrganization
|
// #withCurrentOrganization
|
||||||
organization: { base_currency },
|
organization: { base_currency },
|
||||||
|
|
||||||
@@ -52,9 +47,6 @@ function VendorFormFormik({
|
|||||||
isNewMode,
|
isNewMode,
|
||||||
} = useVendorFormContext();
|
} = useVendorFormContext();
|
||||||
|
|
||||||
/**
|
|
||||||
* Initial values in create and edit mode.
|
|
||||||
*/
|
|
||||||
const initialFormValues = useMemo(
|
const initialFormValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...defaultInitialValues,
|
...defaultInitialValues,
|
||||||
@@ -106,51 +98,34 @@ function VendorFormFormik({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
CLASSES.PAGE_FORM,
|
|
||||||
CLASSES.PAGE_FORM_VENDOR,
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Formik
|
<Formik
|
||||||
validationSchema={
|
validationSchema={
|
||||||
isNewMode ? CreateVendorFormSchema : EditVendorFormSchema
|
isNewMode ? CreateVendorFormSchema : EditVendorFormSchema
|
||||||
}
|
}
|
||||||
initialValues={initialFormValues}
|
initialValues={initialFormValues}
|
||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<VendorFormHeaderPrimary>
|
<VendorFormFields>
|
||||||
<VendorFormPrimarySection />
|
<VendorFormContent onCancel={onCancel} />
|
||||||
</VendorFormHeaderPrimary>
|
</VendorFormFields>
|
||||||
|
|
||||||
<div className={'page-form__after-priamry-section'}>
|
|
||||||
<VendorFormAfterPrimarySection />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
|
||||||
<VendorTabs vendor={vendorId} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<VendorFloatingActions onCancel={onCancel} />
|
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VendorFormHeaderPrimary = styled.div`
|
|
||||||
--x-color-border: #e4e4e4;
|
|
||||||
|
|
||||||
.bp4-dark & {
|
const VendorFormFields = styled.div`
|
||||||
--x-color-border: var(--color-dark-gray3);
|
.bp4-form-content,
|
||||||
|
.bp6-form-content {
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
.bp4-form-group{
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.bp4-form-group.bp4-inline label.bp4-label {
|
||||||
|
min-width: 140px;
|
||||||
}
|
}
|
||||||
padding: 10px 0 0;
|
|
||||||
margin: 0 0 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
border-bottom: 1px solid var(--x-color-border);
|
|
||||||
max-width: 1000px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default compose(withCurrentOrganization())(VendorFormFormik);
|
export const VendorFormFormik = compose(withCurrentOrganization())(VendorFormFormikBase);
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { Box, FFormGroup, FormattedMessage as T, FTextArea } from '@/components';
|
||||||
|
import { VendorFormSectionTitle } from './VendorFormSectionTitle';
|
||||||
|
|
||||||
|
export function VendorFormNotesSection() {
|
||||||
|
return (
|
||||||
|
<Box data-section-id="notes">
|
||||||
|
<VendorFormSectionTitle>
|
||||||
|
<T id={'notes'} />
|
||||||
|
</VendorFormSectionTitle>
|
||||||
|
|
||||||
|
<FFormGroup name={'note'} label={<T id={'note'} />} inline fill fastField>
|
||||||
|
<FTextArea name={'note'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,12 +2,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useParams, useHistory } from 'react-router-dom';
|
import { useParams, useHistory } from 'react-router-dom';
|
||||||
|
import { Box, DashboardCard, DashboardInsider } from '@/components';
|
||||||
import '@/style/pages/Vendors/PageForm.scss';
|
|
||||||
|
|
||||||
import { DashboardCard, DashboardInsider } from '@/components';
|
|
||||||
import { VendorFormProvider, useVendorFormContext } from './VendorFormProvider';
|
import { VendorFormProvider, useVendorFormContext } from './VendorFormProvider';
|
||||||
import VendorFormFormik from './VendorFormFormik';
|
import { VendorFormFormik } from './VendorFormFormik';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor form page loading wrapper.
|
* Vendor form page loading wrapper.
|
||||||
@@ -17,16 +14,16 @@ function VendorFormPageLoading({ children }) {
|
|||||||
const { isFormLoading } = useVendorFormContext();
|
const { isFormLoading } = useVendorFormContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VendorDashboardInsider loading={isFormLoading}>
|
<DashboardInsider loading={isFormLoading}>
|
||||||
{children}
|
{children}
|
||||||
</VendorDashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor form page.
|
* Vendor form page.
|
||||||
*/
|
*/
|
||||||
export default function VendorFormPage() {
|
export function VendorFormPage() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
@@ -44,26 +41,13 @@ export default function VendorFormPage() {
|
|||||||
return (
|
return (
|
||||||
<VendorFormProvider vendorId={id}>
|
<VendorFormProvider vendorId={id}>
|
||||||
<VendorFormPageLoading>
|
<VendorFormPageLoading>
|
||||||
<DashboardCard page>
|
<Box mx={'auto'} maxWidth={800}>
|
||||||
<VendorFormPageFormik
|
<VendorFormFormik
|
||||||
onSubmitSuccess={handleSubmitSuccess}
|
onSubmitSuccess={handleSubmitSuccess}
|
||||||
onCancel={handleFormCancel}
|
onCancel={handleFormCancel}
|
||||||
/>
|
/>
|
||||||
</DashboardCard>
|
</Box>
|
||||||
</VendorFormPageLoading>
|
</VendorFormPageLoading>
|
||||||
</VendorFormProvider>
|
</VendorFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const VendorFormPageFormik = styled(VendorFormFormik)`
|
|
||||||
.page-form {
|
|
||||||
&__floating-actions {
|
|
||||||
margin-left: -40px;
|
|
||||||
margin-right: -40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const VendorDashboardInsider = styled(DashboardInsider)`
|
|
||||||
padding-bottom: 64px;
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { ControlGroup } from '@blueprintjs/core';
|
|
||||||
import {
|
|
||||||
FormattedMessage as T,
|
|
||||||
FFormGroup,
|
|
||||||
FInputGroup,
|
|
||||||
Hint,
|
|
||||||
FieldRequiredHint,
|
|
||||||
SalutationList,
|
|
||||||
DisplayNameList,
|
|
||||||
} from '@/components';
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
import { useAutofocus } from '@/hooks';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vendor form primary section.
|
|
||||||
*/
|
|
||||||
function VendorFormPrimarySection() {
|
|
||||||
const firstNameFieldRef = useAutofocus();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={'customer-form__primary-section-content'}>
|
|
||||||
{/**----------- Vendor name -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'salutation'}
|
|
||||||
className={classNames('form-group--contact_name')}
|
|
||||||
label={<T id={'contact_name'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<ControlGroup>
|
|
||||||
<SalutationList
|
|
||||||
name={'salutation'}
|
|
||||||
popoverProps={{ minimal: true }}
|
|
||||||
/>
|
|
||||||
<FInputGroup
|
|
||||||
name={'first_name'}
|
|
||||||
placeholder={intl.get('first_name')}
|
|
||||||
className={classNames('input-group--first-name')}
|
|
||||||
inputRef={(ref) => (firstNameFieldRef.current = ref)}
|
|
||||||
/>
|
|
||||||
<FInputGroup
|
|
||||||
name={'last_name'}
|
|
||||||
placeholder={intl.get('last_name')}
|
|
||||||
className={classNames('input-group--last-name')}
|
|
||||||
/>
|
|
||||||
</ControlGroup>
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*----------- Company Name -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'company_name'}
|
|
||||||
className={classNames('form-group--company_name')}
|
|
||||||
label={<T id={'company_name'} />}
|
|
||||||
inline={true}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'company_name'} />
|
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
{/*----------- Display Name -----------*/}
|
|
||||||
<FFormGroup
|
|
||||||
name={'display_name'}
|
|
||||||
label={
|
|
||||||
<>
|
|
||||||
<T id={'display_name'} />
|
|
||||||
<FieldRequiredHint />
|
|
||||||
<Hint />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
fastField
|
|
||||||
inline
|
|
||||||
>
|
|
||||||
<DisplayNameList
|
|
||||||
name={'display_name'}
|
|
||||||
popoverProps={{ minimal: true }}
|
|
||||||
/>
|
|
||||||
</FFormGroup>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default VendorFormPrimarySection;
|
|
||||||
@@ -33,7 +33,6 @@ function VendorFormProvider({ query, vendorId, ...props }) {
|
|||||||
const { data: vendor, isLoading: isVendorLoading } = useVendor(vendorId, {
|
const { data: vendor, isLoading: isVendorLoading } = useVendor(vendorId, {
|
||||||
enabled: !!vendorId,
|
enabled: !!vendorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle fetch contact duplicate details.
|
// Handle fetch contact duplicate details.
|
||||||
const { data: contactDuplicate, isLoading: isContactLoading } = useContact(
|
const { data: contactDuplicate, isLoading: isContactLoading } = useContact(
|
||||||
contactId,
|
contactId,
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
const vendorFormSectionTitleClass = css`
|
||||||
|
font-size: 14px;
|
||||||
|
color: #8f99a8;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
margin-top: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function VendorFormSectionTitle({ children }: { children: React.ReactNode | string }) {
|
||||||
|
return <h4 className={vendorFormSectionTitleClass}>{children}</h4>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { Box } from '@/components';
|
||||||
|
import {
|
||||||
|
FormattedMessage as T,
|
||||||
|
FFormGroup,
|
||||||
|
FInputGroup,
|
||||||
|
FTextArea,
|
||||||
|
} from '@/components';
|
||||||
|
import { VendorFormSectionTitle } from './VendorFormSectionTitle';
|
||||||
|
|
||||||
|
export function VendorShippingAddress() {
|
||||||
|
return (
|
||||||
|
<Box data-section-id="shippingAddress">
|
||||||
|
<VendorFormSectionTitle>
|
||||||
|
<T id={'shipping_address'} />
|
||||||
|
</VendorFormSectionTitle>
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address_country'}
|
||||||
|
label={<T id={'country'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'shipping_address_country'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address1'}
|
||||||
|
label={<T id={'address_line_1'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FTextArea name={'shipping_address1'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address2'}
|
||||||
|
label={<T id={'address_line_2'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FTextArea name={'shipping_address2'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address_city'}
|
||||||
|
label={<T id={'city_town'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'shipping_address_city'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address_state'}
|
||||||
|
label={<T id={'state'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'shipping_address_state'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address_postcode'}
|
||||||
|
label={<T id={'zip_code'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'shipping_address_postcode'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup
|
||||||
|
name={'shipping_address_phone'}
|
||||||
|
label={<T id={'phone'} />}
|
||||||
|
inline
|
||||||
|
fill
|
||||||
|
fastField
|
||||||
|
>
|
||||||
|
<FInputGroup name={'shipping_address_phone'} fill fastField />
|
||||||
|
</FFormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Tabs, Tab } from '@blueprintjs/core';
|
|
||||||
import { CLASSES } from '@/constants/classes';
|
|
||||||
|
|
||||||
import VendorFinanicalPanelTab from './VendorFinanicalPanelTab';
|
|
||||||
|
|
||||||
import CustomerAddressTabs from '@/containers/Customers/CustomerForm/CustomerAddressTabs';
|
|
||||||
import CustomerNotePanel from '@/containers/Customers/CustomerForm/CustomerNotePanel';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vendor form tabs.
|
|
||||||
*/
|
|
||||||
export default function VendorTabs() {
|
|
||||||
return (
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_TABS)}>
|
|
||||||
<Tabs
|
|
||||||
animate={true}
|
|
||||||
id={'vendor-tabs'}
|
|
||||||
large={true}
|
|
||||||
defaultSelectedTabId="financial"
|
|
||||||
>
|
|
||||||
<Tab
|
|
||||||
id={'financial'}
|
|
||||||
title={intl.get('financial_details')}
|
|
||||||
panel={<VendorFinanicalPanelTab />}
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
id={'address'}
|
|
||||||
title={intl.get('address')}
|
|
||||||
panel={<CustomerAddressTabs />}
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
id="notes"
|
|
||||||
title={intl.get('notes')}
|
|
||||||
panel={<CustomerNotePanel />}
|
|
||||||
/>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ export const defaultInitialValues = {
|
|||||||
last_name: '',
|
last_name: '',
|
||||||
company_name: '',
|
company_name: '',
|
||||||
display_name: '',
|
display_name: '',
|
||||||
|
code: '',
|
||||||
|
|
||||||
email: '',
|
email: '',
|
||||||
work_phone: '',
|
work_phone: '',
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export function useUncategorizeTransactionsBulkAction(
|
|||||||
UncategorizeTransactionsBulkValues
|
UncategorizeTransactionsBulkValues
|
||||||
>(
|
>(
|
||||||
(value) =>
|
(value) =>
|
||||||
apiRequest.post(`/cashflow/transactions/uncategorize/bulk`, {
|
apiRequest.delete(`/banking/categorize/bulk`, {
|
||||||
ids: value.ids,
|
params: { uncategorizedTransactionIds: value.ids },
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
onSuccess: (res, values) => {
|
onSuccess: (res, values) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import useApiRequest from './useRequest';
|
import useApiRequest from './useRequest';
|
||||||
|
import { normalizeApiPath } from '../utils';
|
||||||
|
|
||||||
export const useRequestPdf = (httpProps) => {
|
export const useRequestPdf = (httpProps) => {
|
||||||
const apiRequest = useApiRequest();
|
const apiRequest = useApiRequest();
|
||||||
@@ -17,7 +18,7 @@ export const useRequestPdf = (httpProps) => {
|
|||||||
headers: { accept: 'application/pdf' },
|
headers: { accept: 'application/pdf' },
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
...httpProps,
|
...httpProps,
|
||||||
url: `/api/${httpProps?.url}`,
|
url: `/api/${normalizeApiPath(httpProps?.url)}`,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// Create a Blob from the PDF Stream.
|
// Create a Blob from the PDF Stream.
|
||||||
|
|||||||
@@ -1997,8 +1997,8 @@
|
|||||||
"vendor_opening_balance.label": "Edit Vendor Opening Balance",
|
"vendor_opening_balance.label": "Edit Vendor Opening Balance",
|
||||||
"vendor_opening_balance.label.opening_balance": "Opening balance",
|
"vendor_opening_balance.label.opening_balance": "Opening balance",
|
||||||
"vendor_opening_balance.label.opening_balance_at": "Opening balance at",
|
"vendor_opening_balance.label.opening_balance_at": "Opening balance at",
|
||||||
"customer.label.opening_branch": "Opening Balance Branch",
|
"customer.label.opening_branch": "Balance Branch",
|
||||||
"vendor.label.opening_branch": "Opening Balance Branch",
|
"vendor.label.opening_branch": "Balance Branch",
|
||||||
"warehouse.error.warehouse_code_not_unique": "Warehouse code not unique",
|
"warehouse.error.warehouse_code_not_unique": "Warehouse code not unique",
|
||||||
"warehouse.error.warehouse_has_associated_transactions": "You could not delete the warehouse that has associated transactions.",
|
"warehouse.error.warehouse_has_associated_transactions": "You could not delete the warehouse that has associated transactions.",
|
||||||
"branche.error.warehouse_code_not_unique": "Branch code not unique",
|
"branche.error.warehouse_code_not_unique": "Branch code not unique",
|
||||||
|
|||||||
@@ -619,7 +619,7 @@ export const getDashboardRoutes = () => [
|
|||||||
{
|
{
|
||||||
path: `/vendors/:id/edit`,
|
path: `/vendors/:id/edit`,
|
||||||
component: lazy(
|
component: lazy(
|
||||||
() => import('@/containers/Vendors/VendorForm/VendorFormPage'),
|
() => import('@/containers/Vendors/VendorForm/VendorFormPage').then(module => ({ default: module.VendorFormPage })),
|
||||||
),
|
),
|
||||||
name: 'vendor-edit',
|
name: 'vendor-edit',
|
||||||
breadcrumb: intl.get('edit_vendor'),
|
breadcrumb: intl.get('edit_vendor'),
|
||||||
@@ -631,7 +631,7 @@ export const getDashboardRoutes = () => [
|
|||||||
{
|
{
|
||||||
path: `/vendors/new`,
|
path: `/vendors/new`,
|
||||||
component: lazy(
|
component: lazy(
|
||||||
() => import('@/containers/Vendors/VendorForm/VendorFormPage'),
|
() => import('@/containers/Vendors/VendorForm/VendorFormPage').then(module => ({ default: module.VendorFormPage })),
|
||||||
),
|
),
|
||||||
name: 'vendor-new',
|
name: 'vendor-new',
|
||||||
breadcrumb: intl.get('new_vendor'),
|
breadcrumb: intl.get('new_vendor'),
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
|
|
||||||
|
&.bp4-outlined {
|
||||||
|
.bp4-dark & {
|
||||||
|
border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bp4-button:not([class*='bp4-intent-']) {
|
.bp4-button:not([class*='bp4-intent-']) {
|
||||||
|
|||||||
@@ -1,156 +0,0 @@
|
|||||||
@import '../../_base.scss';
|
|
||||||
|
|
||||||
.page-form--customer {
|
|
||||||
$self: '.page-form';
|
|
||||||
padding: 20px;
|
|
||||||
|
|
||||||
--x-color-tabs-border: #f0f0f0;
|
|
||||||
|
|
||||||
.bp4-dark & {
|
|
||||||
--x-color-tabs-border: var(--color-dark-gray3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#{$self}__header {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
#{$self}__primary-section {
|
|
||||||
padding: 10px 0 0;
|
|
||||||
margin: 0 0 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
border-bottom: 1px solid #e4e4e4;
|
|
||||||
max-width: 1000px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-form-group {
|
|
||||||
max-width: 500px;
|
|
||||||
|
|
||||||
.bp4-control {
|
|
||||||
margin-top: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.bp4-inline {
|
|
||||||
.bp4-label {
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bp4-form-content {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group--contact_name {
|
|
||||||
max-width: 600px;
|
|
||||||
|
|
||||||
.bp4-control-group > * {
|
|
||||||
flex-shrink: unset;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
&.input-group--salutation-list {
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
&.input-group--first-name,
|
|
||||||
&.input-group--last-name {
|
|
||||||
width: 37%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-form-group {
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-tab-panel {
|
|
||||||
margin-top: 26px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group--phone-number {
|
|
||||||
.bp4-control-group > * {
|
|
||||||
flex-shrink: unset;
|
|
||||||
padding-right: 5px;
|
|
||||||
padding-left: 5px;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
&:last-child {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#{$self}__tabs {
|
|
||||||
margin-top: 20px;
|
|
||||||
max-width: 1000px;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #888;
|
|
||||||
margin-bottom: 1.2rem;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
// Tab panels.
|
|
||||||
.tab-panel {
|
|
||||||
&--address {
|
|
||||||
.bp4-form-group {
|
|
||||||
max-width: 440px;
|
|
||||||
|
|
||||||
&.bp4-inline {
|
|
||||||
.bp4-label {
|
|
||||||
min-width: 145px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-form-content {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea.bp4-input {
|
|
||||||
max-width: 100%;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&--note {
|
|
||||||
.form-group--note {
|
|
||||||
.bp4-form-group {
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone-container {
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bp4-tabs {
|
|
||||||
.bp4-tab-list {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 2px;
|
|
||||||
background: var(--x-color-tabs-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
> *:not(:last-child) {
|
|
||||||
margin-right: 25px;
|
|
||||||
}
|
|
||||||
&.bp4-large > .bp4-tab {
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+255
-21
@@ -398,6 +398,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number for pagination",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of items per page",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -476,24 +494,6 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pageSize",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"description": "Number of items per page",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "page",
|
|
||||||
"required": false,
|
|
||||||
"in": "query",
|
|
||||||
"description": "Page number for pagination",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -2999,6 +2999,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -4848,6 +4866,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -5964,6 +6000,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -6357,6 +6411,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -7140,6 +7212,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -7458,6 +7548,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -8027,6 +8135,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -8795,6 +8921,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -9385,6 +9529,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -10114,6 +10276,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -10489,6 +10669,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -11518,6 +11716,24 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page number (1-based)",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"description": "Page size",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "customViewId",
|
"name": "customViewId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@@ -15262,7 +15478,7 @@
|
|||||||
},
|
},
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"name": "Current Liabilties",
|
"name": "Current Liabilities",
|
||||||
"id": "CURRENT_LIABILITY",
|
"id": "CURRENT_LIABILITY",
|
||||||
"node_type": "AGGREGATE",
|
"node_type": "AGGREGATE",
|
||||||
"type": "AGGREGATE",
|
"type": "AGGREGATE",
|
||||||
@@ -15951,7 +16167,7 @@
|
|||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"key": "name",
|
"key": "name",
|
||||||
"value": "Current Liabilties"
|
"value": "Current Liabilities"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "total",
|
"key": "total",
|
||||||
@@ -16079,7 +16295,7 @@
|
|||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"key": "name",
|
"key": "name",
|
||||||
"value": "Total Current Liabilties"
|
"value": "Total Current Liabilities"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "total",
|
"key": "total",
|
||||||
@@ -29171,6 +29387,11 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Active status",
|
"description": "Active status",
|
||||||
"default": true
|
"default": true
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Customer code",
|
||||||
|
"example": "CUST-001"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -29293,6 +29514,10 @@
|
|||||||
"active": {
|
"active": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Active status"
|
"description": "Active status"
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Customer code"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -29528,6 +29753,11 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether the vendor is active",
|
"description": "Whether the vendor is active",
|
||||||
"default": true
|
"default": true
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Vendor code",
|
||||||
|
"example": "VEND-001"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -29644,6 +29874,10 @@
|
|||||||
"active": {
|
"active": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether the vendor is active"
|
"description": "Whether the vendor is active"
|
||||||
|
},
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Vendor code"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8390,6 +8390,11 @@ export interface components {
|
|||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
/**
|
||||||
|
* @description Customer code
|
||||||
|
* @example CUST-001
|
||||||
|
*/
|
||||||
|
code?: string;
|
||||||
};
|
};
|
||||||
EditCustomerDto: {
|
EditCustomerDto: {
|
||||||
/** @description Billing address line 1 */
|
/** @description Billing address line 1 */
|
||||||
@@ -8448,6 +8453,8 @@ export interface components {
|
|||||||
note?: string;
|
note?: string;
|
||||||
/** @description Active status */
|
/** @description Active status */
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
/** @description Customer code */
|
||||||
|
code?: string;
|
||||||
};
|
};
|
||||||
CustomerOpeningBalanceEditDto: {
|
CustomerOpeningBalanceEditDto: {
|
||||||
/**
|
/**
|
||||||
@@ -8588,6 +8595,11 @@ export interface components {
|
|||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
/**
|
||||||
|
* @description Vendor code
|
||||||
|
* @example VEND-001
|
||||||
|
*/
|
||||||
|
code?: string;
|
||||||
};
|
};
|
||||||
EditVendorDto: {
|
EditVendorDto: {
|
||||||
/** @description Billing address line 1 */
|
/** @description Billing address line 1 */
|
||||||
@@ -8644,6 +8656,8 @@ export interface components {
|
|||||||
note?: string;
|
note?: string;
|
||||||
/** @description Whether the vendor is active */
|
/** @description Whether the vendor is active */
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
/** @description Vendor code */
|
||||||
|
code?: string;
|
||||||
};
|
};
|
||||||
VendorOpeningBalanceEditDto: {
|
VendorOpeningBalanceEditDto: {
|
||||||
/**
|
/**
|
||||||
@@ -14608,6 +14622,10 @@ export interface operations {
|
|||||||
ItemsController_getItems: {
|
ItemsController_getItems: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number for pagination */
|
||||||
|
page?: number;
|
||||||
|
/** @description Number of items per page */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID for filtering */
|
/** @description Custom view ID for filtering */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Array of filter roles */
|
/** @description Array of filter roles */
|
||||||
@@ -14624,10 +14642,6 @@ export interface operations {
|
|||||||
viewSlug?: string;
|
viewSlug?: string;
|
||||||
/** @description Filter for inactive items */
|
/** @description Filter for inactive items */
|
||||||
inactiveMode?: boolean;
|
inactiveMode?: boolean;
|
||||||
/** @description Number of items per page */
|
|
||||||
pageSize?: number;
|
|
||||||
/** @description Page number for pagination */
|
|
||||||
page?: number;
|
|
||||||
};
|
};
|
||||||
header: {
|
header: {
|
||||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||||
@@ -16083,6 +16097,10 @@ export interface operations {
|
|||||||
SaleInvoicesController_getSaleInvoices: {
|
SaleInvoicesController_getSaleInvoices: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -17210,6 +17228,10 @@ export interface operations {
|
|||||||
PaymentReceivesController_getPaymentsReceived: {
|
PaymentReceivesController_getPaymentsReceived: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -17870,6 +17892,10 @@ export interface operations {
|
|||||||
ItemCategoryController_getItemCategories: {
|
ItemCategoryController_getItemCategories: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -18071,6 +18097,10 @@ export interface operations {
|
|||||||
ExpensesController_getExpenses: {
|
ExpensesController_getExpenses: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -18510,6 +18540,10 @@ export interface operations {
|
|||||||
CustomersController_getCustomers: {
|
CustomersController_getCustomers: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -18666,6 +18700,10 @@ export interface operations {
|
|||||||
VendorsController_getVendors: {
|
VendorsController_getVendors: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -18945,6 +18983,10 @@ export interface operations {
|
|||||||
SaleEstimatesController_getSaleEstimates: {
|
SaleEstimatesController_getSaleEstimates: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -19369,6 +19411,10 @@ export interface operations {
|
|||||||
SaleReceiptsController_getSaleReceipts: {
|
SaleReceiptsController_getSaleReceipts: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -19682,6 +19728,10 @@ export interface operations {
|
|||||||
BillsController_getBills: {
|
BillsController_getBills: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -20070,6 +20120,10 @@ export interface operations {
|
|||||||
ManualJournalsController_getManualJournals: {
|
ManualJournalsController_getManualJournals: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -20288,6 +20342,10 @@ export interface operations {
|
|||||||
CreditNotesController_getCreditNotes: {
|
CreditNotesController_getCreditNotes: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -20910,6 +20968,10 @@ export interface operations {
|
|||||||
VendorCreditsController_getVendorCredits: {
|
VendorCreditsController_getVendorCredits: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
/** @description Page number (1-based) */
|
||||||
|
page?: number;
|
||||||
|
/** @description Page size */
|
||||||
|
pageSize?: number;
|
||||||
/** @description Custom view ID */
|
/** @description Custom view ID */
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
/** @description Filter roles */
|
/** @description Filter roles */
|
||||||
@@ -23039,7 +23101,7 @@ export interface operations {
|
|||||||
* },
|
* },
|
||||||
* "children": [
|
* "children": [
|
||||||
* {
|
* {
|
||||||
* "name": "Current Liabilties",
|
* "name": "Current Liabilities",
|
||||||
* "id": "CURRENT_LIABILITY",
|
* "id": "CURRENT_LIABILITY",
|
||||||
* "node_type": "AGGREGATE",
|
* "node_type": "AGGREGATE",
|
||||||
* "type": "AGGREGATE",
|
* "type": "AGGREGATE",
|
||||||
@@ -23726,7 +23788,7 @@ export interface operations {
|
|||||||
* "cells": [
|
* "cells": [
|
||||||
* {
|
* {
|
||||||
* "key": "name",
|
* "key": "name",
|
||||||
* "value": "Current Liabilties"
|
* "value": "Current Liabilities"
|
||||||
* },
|
* },
|
||||||
* {
|
* {
|
||||||
* "key": "total",
|
* "key": "total",
|
||||||
@@ -23854,7 +23916,7 @@ export interface operations {
|
|||||||
* "cells": [
|
* "cells": [
|
||||||
* {
|
* {
|
||||||
* "key": "name",
|
* "key": "name",
|
||||||
* "value": "Total Current Liabilties"
|
* "value": "Total Current Liabilities"
|
||||||
* },
|
* },
|
||||||
* {
|
* {
|
||||||
* "key": "total",
|
* "key": "total",
|
||||||
|
|||||||
Reference in New Issue
Block a user