Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd9fdeb3d9 | |||
| 6c633de22b | |||
| f64c59b9a1 | |||
| a4adb4fb03 | |||
| 985708e87a | |||
| 01cef7a595 | |||
| 18ab1f6ae3 | |||
| 5ea3ac14e4 | |||
| 84079df772 | |||
| 28a8b0b5d1 | |||
|
|
2c05785096 | ||
|
|
6af4be9c6c | ||
|
|
8def1d31d2 | ||
|
|
afab02a053 | ||
|
|
8e925c62f2 | ||
|
|
1b7d513adf | ||
|
|
7d764fb390 | ||
|
|
c571f50a74 | ||
|
|
6549026344 | ||
|
|
0963394b04 | ||
|
|
6cab0651fc | ||
|
|
4af537d6dd | ||
|
|
34db64612c | ||
|
|
10225bbfed | ||
|
|
c3a4fe6b37 | ||
|
|
02be959461 | ||
|
|
d5bf56e333 | ||
|
|
e3182c15b3 |
@@ -9,21 +9,21 @@ services:
|
||||
- server
|
||||
- webapp
|
||||
ports:
|
||||
- '${PUBLIC_PROXY_PORT:-80}:80'
|
||||
- '${PUBLIC_PROXY_SSL_PORT:-443}:443'
|
||||
- '8085:80'
|
||||
- '8443:443'
|
||||
tty: true
|
||||
volumes:
|
||||
- ./docker/envoy/envoy.yaml:/etc/envoy/envoy.yaml
|
||||
restart: on-failure
|
||||
networks:
|
||||
- bigcapital_network
|
||||
- coolify
|
||||
|
||||
webapp:
|
||||
container_name: bigcapital-webapp
|
||||
image: bigcapitalhq/webapp:latest
|
||||
restart: on-failure
|
||||
networks:
|
||||
- bigcapital_network
|
||||
- coolify
|
||||
|
||||
server:
|
||||
container_name: bigcapital-server
|
||||
@@ -38,7 +38,7 @@ services:
|
||||
- redis
|
||||
restart: on-failure
|
||||
networks:
|
||||
- bigcapital_network
|
||||
- coolify
|
||||
environment:
|
||||
# Mail
|
||||
- MAIL_HOST=${MAIL_HOST}
|
||||
@@ -144,8 +144,8 @@ services:
|
||||
depends_on:
|
||||
- mysql
|
||||
networks:
|
||||
- bigcapital_network
|
||||
|
||||
- coolify
|
||||
|
||||
mysql:
|
||||
container_name: bigcapital-mysql
|
||||
restart: on-failure
|
||||
@@ -158,10 +158,12 @@ services:
|
||||
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- mysql:/var/lib/mysql
|
||||
ports:
|
||||
- '3335:3306'
|
||||
expose:
|
||||
- '3306'
|
||||
networks:
|
||||
- bigcapital_network
|
||||
- coolify
|
||||
|
||||
redis:
|
||||
container_name: bigcapital-redis
|
||||
@@ -173,14 +175,14 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
networks:
|
||||
- bigcapital_network
|
||||
- coolify
|
||||
|
||||
gotenberg:
|
||||
image: gotenberg/gotenberg:7
|
||||
expose:
|
||||
- '9000'
|
||||
networks:
|
||||
- bigcapital_network
|
||||
- coolify
|
||||
|
||||
# Volumes
|
||||
volumes:
|
||||
@@ -194,5 +196,5 @@ volumes:
|
||||
|
||||
# Networks
|
||||
networks:
|
||||
bigcapital_network:
|
||||
driver: bridge
|
||||
coolify:
|
||||
external: true
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"deleteOutDir": true,
|
||||
"assets": [
|
||||
{ "include": "i18n/**/*", "watchAssets": true },
|
||||
{ "include": "database/**/*", "watchAssets": true }
|
||||
{ "include": "database/**/*", "exclude": "**/*.ts", "watchAssets": true }
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Scope } from '@nestjs/common';
|
||||
import {
|
||||
SendResetPasswordMailJob,
|
||||
SendResetPasswordMailQueue,
|
||||
} from '../Auth.constants';
|
||||
import { Process } from '@nestjs/bull';
|
||||
import { SendResetPasswordMailQueue } from '../Auth.constants';
|
||||
import { Job } from 'bullmq';
|
||||
import { AuthenticationMailMesssages } from '../AuthMailMessages.esrvice';
|
||||
import { MailTransporter } from '@/modules/Mail/MailTransporter.service';
|
||||
@@ -23,7 +19,6 @@ export class SendResetPasswordMailProcessor extends WorkerHost {
|
||||
super();
|
||||
}
|
||||
|
||||
@Process(SendResetPasswordMailJob)
|
||||
async process(job: Job<SendResetPasswordMailJobPayload>) {
|
||||
try {
|
||||
await this.authMailMesssages.sendResetPasswordMail(
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { Job } from 'bullmq';
|
||||
import { Process } from '@nestjs/bull';
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import {
|
||||
SendSignupVerificationMailJob,
|
||||
SendSignupVerificationMailQueue,
|
||||
} from '../Auth.constants';
|
||||
import { SendSignupVerificationMailQueue } from '../Auth.constants';
|
||||
import { MailTransporter } from '@/modules/Mail/MailTransporter.service';
|
||||
import { AuthenticationMailMesssages } from '../AuthMailMessages.esrvice';
|
||||
|
||||
@@ -21,7 +17,6 @@ export class SendSignupVerificationMailProcessor extends WorkerHost {
|
||||
super();
|
||||
}
|
||||
|
||||
@Process(SendSignupVerificationMailJob)
|
||||
async process(job: Job<SendSignupVerificationMailJobPayload>) {
|
||||
try {
|
||||
await this.authMailMesssages.sendSignupVerificationMail(
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Process } from '@nestjs/bull';
|
||||
import { UseCls } from 'nestjs-cls';
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { Job } from 'bullmq';
|
||||
import {
|
||||
PlaidFetchTransitonsEventPayload,
|
||||
UpdateBankingPlaidTransitionsJob,
|
||||
UpdateBankingPlaidTransitionsQueueJob,
|
||||
} from '../types/BankingPlaid.types';
|
||||
import { PlaidUpdateTransactions } from '../command/PlaidUpdateTransactions';
|
||||
@@ -28,7 +26,6 @@ export class PlaidFetchTransactionsProcessor extends WorkerHost {
|
||||
/**
|
||||
* Triggers the function.
|
||||
*/
|
||||
@Process(UpdateBankingPlaidTransitionsJob)
|
||||
@UseCls()
|
||||
async process(job: Job<PlaidFetchTransitonsEventPayload>) {
|
||||
const { plaidItemId } = job.data;
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
RecognizeUncategorizedTransactionsJobPayload,
|
||||
RecognizeUncategorizedTransactionsQueue,
|
||||
} from '../_types';
|
||||
import { Process } from '@nestjs/bull';
|
||||
|
||||
@Processor({
|
||||
name: RecognizeUncategorizedTransactionsQueue,
|
||||
@@ -28,7 +27,6 @@ export class RegonizeTransactionsPrcessor extends WorkerHost {
|
||||
/**
|
||||
* Triggers sending invoice mail.
|
||||
*/
|
||||
@Process(RecognizeUncategorizedTransactionsQueue)
|
||||
@UseCls()
|
||||
async process(job: Job<RecognizeUncategorizedTransactionsJobPayload>) {
|
||||
const { ruleId, transactionsCriteria } = job.data;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
|
||||
import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto';
|
||||
import { BranchResponseDto } from '@/modules/Branches/dtos/BranchResponse.dto';
|
||||
import { DiscountType } from '@/common/types/Discount';
|
||||
|
||||
export class BillResponseDto {
|
||||
@@ -89,6 +91,14 @@ export class BillResponseDto {
|
||||
})
|
||||
branchId?: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Branch details',
|
||||
type: () => BranchResponseDto,
|
||||
required: false,
|
||||
})
|
||||
@Type(() => BranchResponseDto)
|
||||
branch?: BranchResponseDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'The ID of the project',
|
||||
example: 301,
|
||||
|
||||
@@ -30,6 +30,7 @@ export class BillTransformer extends Transformer {
|
||||
'taxes',
|
||||
'entries',
|
||||
'attachments',
|
||||
'branch',
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@ import { ValidateBranchExistance } from './integrations/ValidateBranchExistance'
|
||||
import { ManualJournalBranchesValidator } from './integrations/ManualJournals/ManualJournalsBranchesValidator';
|
||||
import { CashflowTransactionsActivateBranches } from './integrations/Cashflow/CashflowActivateBranches';
|
||||
import { ExpensesActivateBranches } from './integrations/Expense/ExpensesActivateBranches';
|
||||
import { BillActivateBranches } from './integrations/Purchases/BillBranchesActivate';
|
||||
import { VendorCreditActivateBranches } from './integrations/Purchases/VendorCreditBranchesActivate';
|
||||
import { BillPaymentsActivateBranches } from './integrations/Purchases/PaymentMadeBranchesActivate';
|
||||
import { BillBranchesActivateSubscriber } from './subscribers/Activate/BillBranchesActivateSubscriber';
|
||||
import { VendorCreditBranchesActivateSubscriber } from './subscribers/Activate/VendorCreditBranchesActivateSubscriber';
|
||||
import { PaymentMadeActivateBranchesSubscriber } from './subscribers/Activate/PaymentMadeBranchesActivateSubscriber';
|
||||
import { FeaturesModule } from '../Features/Features.module';
|
||||
|
||||
@Module({
|
||||
@@ -66,7 +72,13 @@ import { FeaturesModule } from '../Features/Features.module';
|
||||
ValidateBranchExistance,
|
||||
ManualJournalBranchesValidator,
|
||||
CashflowTransactionsActivateBranches,
|
||||
ExpensesActivateBranches
|
||||
ExpensesActivateBranches,
|
||||
BillActivateBranches,
|
||||
VendorCreditActivateBranches,
|
||||
BillPaymentsActivateBranches,
|
||||
BillBranchesActivateSubscriber,
|
||||
VendorCreditBranchesActivateSubscriber,
|
||||
PaymentMadeActivateBranchesSubscriber
|
||||
],
|
||||
exports: [
|
||||
BranchesSettingsService,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { Bill } from '@/modules/Bills/models/Bill';
|
||||
|
||||
@Injectable()
|
||||
export class BillActivateBranches {
|
||||
constructor(private readonly billModel: TenantModelProxy<typeof Bill>) {}
|
||||
constructor(
|
||||
@Inject(Bill.name)
|
||||
private readonly billModel: TenantModelProxy<typeof Bill>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Updates all bills transactions with the primary branch.
|
||||
@@ -17,7 +20,7 @@ export class BillActivateBranches {
|
||||
primaryBranchId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
// Updates the sale invoice with primary branch.
|
||||
await Bill.query(trx).update({ branchId: primaryBranchId });
|
||||
// Updates the bills with primary branch.
|
||||
await this.billModel().query(trx).update({ branchId: primaryBranchId });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { BillPayment } from '@/modules/BillPayments/models/BillPayment';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class BillPaymentsActivateBranches {
|
||||
constructor(
|
||||
@Inject(BillPayment.name)
|
||||
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit';
|
||||
|
||||
@Injectable()
|
||||
export class VendorCreditActivateBranches {
|
||||
constructor(
|
||||
@Inject(VendorCredit.name)
|
||||
private readonly vendorCreditModel: TenantModelProxy<typeof VendorCredit>,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { IBranchesActivatedPayload } from '../../Branches.types';
|
||||
import { events } from '@/common/events/events';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BillActivateBranches } from '../../integrations/Purchases/BillBranchesActivate';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
@Injectable()
|
||||
export class BillBranchesActivateSubscriber {
|
||||
constructor(
|
||||
private readonly billActivateBranches: BillActivateBranches,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Updates bills transactions with the primary branch once
|
||||
* the multi-branches is activated.
|
||||
* @param {IBranchesActivatedPayload}
|
||||
*/
|
||||
@OnEvent(events.branch.onActivated)
|
||||
async updateBillsWithBranchOnActivated({
|
||||
primaryBranch,
|
||||
trx,
|
||||
}: IBranchesActivatedPayload) {
|
||||
await this.billActivateBranches.updateBillsWithBranch(
|
||||
primaryBranch.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { IBranchesActivatedPayload } from '../../Branches.types';
|
||||
import { events } from '@/common/events/events';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { VendorCreditActivateBranches } from '../../integrations/Purchases/VendorCreditBranchesActivate';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
@Injectable()
|
||||
export class VendorCreditBranchesActivateSubscriber {
|
||||
constructor(
|
||||
private readonly vendorCreditActivateBranches: VendorCreditActivateBranches,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Updates vendor credits transactions with the primary branch once
|
||||
* the multi-branches is activated.
|
||||
* @param {IBranchesActivatedPayload}
|
||||
*/
|
||||
@OnEvent(events.branch.onActivated)
|
||||
async updateVendorCreditsWithBranchOnActivated({
|
||||
primaryBranch,
|
||||
trx,
|
||||
}: IBranchesActivatedPayload) {
|
||||
await this.vendorCreditActivateBranches.updateVendorCreditsWithBranch(
|
||||
primaryBranch.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ export abstract class BaseCommand extends CommandRunner {
|
||||
},
|
||||
migrations: {
|
||||
directory: this.configService.get('systemDatabase.migrationDir'),
|
||||
loadExtensions: ['.js'],
|
||||
},
|
||||
seeds: {
|
||||
directory: this.configService.get('systemDatabase.seedsDir'),
|
||||
@@ -43,6 +44,7 @@ export abstract class BaseCommand extends CommandRunner {
|
||||
},
|
||||
migrations: {
|
||||
directory: this.configService.get('tenantDatabase.migrationsDir') || './src/database/migrations',
|
||||
loadExtensions: ['.js'],
|
||||
},
|
||||
seeds: {
|
||||
directory: this.configService.get('tenantDatabase.seedsDir') || './src/database/seeds/core',
|
||||
|
||||
@@ -24,14 +24,14 @@ export class BalanceSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
displayColumnsType: 'total' | 'date_periods' = 'total';
|
||||
|
||||
@ApiProperty({
|
||||
enum: ['day', 'month', 'year'],
|
||||
enum: ['day', 'month', 'year', 'quarter'],
|
||||
default: 'year',
|
||||
description: 'Time period for column display',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsEnum(['day', 'month', 'year'])
|
||||
displayColumnsBy: 'day' | 'month' | 'year' = 'year';
|
||||
@IsEnum(['day', 'month', 'year', 'quarter'])
|
||||
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year';
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Start date for the balance sheet period',
|
||||
|
||||
@@ -34,13 +34,13 @@ export class CashFlowStatementQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
@ApiProperty({
|
||||
description: 'Display columns by time period',
|
||||
required: false,
|
||||
enum: ['day', 'month', 'year'],
|
||||
enum: ['day', 'month', 'year', 'quarter'],
|
||||
default: 'year',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsEnum(['day', 'month', 'year'])
|
||||
displayColumnsBy: 'day' | 'month' | 'year' = 'year';
|
||||
@IsEnum(['day', 'month', 'year', 'quarter'])
|
||||
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year';
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Type of column display',
|
||||
|
||||
@@ -64,10 +64,10 @@ export class ProfitLossSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
displayColumnsType: 'total' | 'date_periods';
|
||||
|
||||
@IsString()
|
||||
@IsEnum(['day', 'month', 'year'])
|
||||
@IsEnum(['day', 'month', 'year', 'quarter'])
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'How to display columns' })
|
||||
displayColumnsBy: 'day' | 'month' | 'year' = 'year';
|
||||
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year';
|
||||
|
||||
@Transform(({ value }) => parseBoolean(value, false))
|
||||
@IsBoolean()
|
||||
|
||||
@@ -7,11 +7,7 @@ import * as moment from 'moment';
|
||||
import { TenantJobPayload } from '@/interfaces/Tenant';
|
||||
import { InventoryComputeCostService } from '../commands/InventoryComputeCost.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import {
|
||||
ComputeItemCostQueue,
|
||||
ComputeItemCostQueueJob,
|
||||
} from '../types/InventoryCost.types';
|
||||
import { Process } from '@nestjs/bull';
|
||||
import { ComputeItemCostQueue } from '../types/InventoryCost.types';
|
||||
|
||||
interface ComputeItemCostJobPayload extends TenantJobPayload {
|
||||
itemId: number;
|
||||
@@ -39,7 +35,6 @@ export class ComputeItemCostProcessor extends WorkerHost {
|
||||
* Process the compute item cost job.
|
||||
* @param {Job<ComputeItemCostJobPayload>} job - The job to process
|
||||
*/
|
||||
@Process(ComputeItemCostQueueJob)
|
||||
@UseCls()
|
||||
async process(job: Job<ComputeItemCostJobPayload>) {
|
||||
const { itemId, startingDate, organizationId, userId } = job.data;
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { Process } from '@nestjs/bull';
|
||||
import {
|
||||
WriteInventoryTransactionsGLEntriesQueue,
|
||||
WriteInventoryTransactionsGLEntriesQueueJob,
|
||||
} from '../types/InventoryCost.types';
|
||||
import { WriteInventoryTransactionsGLEntriesQueue } from '../types/InventoryCost.types';
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Scope } from '@nestjs/common';
|
||||
|
||||
@@ -15,6 +11,5 @@ export class WriteInventoryTransactionsGLEntriesProcessor extends WorkerHost {
|
||||
super();
|
||||
}
|
||||
|
||||
@Process(WriteInventoryTransactionsGLEntriesQueueJob)
|
||||
async process() {}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
BulkDeleteItemsDto,
|
||||
ValidateBulkDeleteItemsResponseDto,
|
||||
} from './dtos/BulkDeleteItems.dto';
|
||||
import { ItemApiErrorResponseDto } from './dtos/ItemErrorResponse.dto';
|
||||
|
||||
@Controller('/items')
|
||||
@ApiTags('Items')
|
||||
@@ -45,6 +46,7 @@ import {
|
||||
@ApiExtraModels(ItemEstimatesResponseDto)
|
||||
@ApiExtraModels(ItemReceiptsResponseDto)
|
||||
@ApiExtraModels(ValidateBulkDeleteItemsResponseDto)
|
||||
@ApiExtraModels(ItemApiErrorResponseDto)
|
||||
@ApiCommonHeaders()
|
||||
export class ItemsController extends TenantController {
|
||||
constructor(private readonly itemsApplication: ItemsApplicationService) {
|
||||
@@ -147,6 +149,13 @@ export class ItemsController extends TenantController {
|
||||
status: 200,
|
||||
description: 'The item has been successfully updated.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Validation error. Possible error types: ITEM_NAME_EXISTS, INVENTORY_ACCOUNT_CANNOT_MODIFIED, TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS, etc.',
|
||||
schema: {
|
||||
$ref: getSchemaPath(ItemApiErrorResponseDto),
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'The item not found.' })
|
||||
@ApiParam({
|
||||
name: 'id',
|
||||
@@ -204,6 +213,13 @@ export class ItemsController extends TenantController {
|
||||
status: 200,
|
||||
description: 'The item has been successfully created.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Validation error. Possible error types: ITEM_NAME_EXISTS, ITEM_CATEOGRY_NOT_FOUND, COST_ACCOUNT_NOT_COGS, SELL_ACCOUNT_NOT_INCOME, INVENTORY_ACCOUNT_NOT_INVENTORY, INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM, COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM, etc.',
|
||||
schema: {
|
||||
$ref: getSchemaPath(ItemApiErrorResponseDto),
|
||||
},
|
||||
})
|
||||
// @UsePipes(new ZodValidationPipe(createItemSchema))
|
||||
async createItem(
|
||||
@Body() createItemDto: CreateItemDto,
|
||||
@@ -219,6 +235,13 @@ export class ItemsController extends TenantController {
|
||||
status: 200,
|
||||
description: 'The item has been successfully deleted.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Cannot delete item. Possible error types: ITEM_HAS_ASSOCIATED_TRANSACTINS, ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT, etc.',
|
||||
schema: {
|
||||
$ref: getSchemaPath(ItemApiErrorResponseDto),
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'The item not found.' })
|
||||
@ApiParam({
|
||||
name: 'id',
|
||||
|
||||
112
packages/server/src/modules/Items/dtos/ItemErrorResponse.dto.ts
Normal file
112
packages/server/src/modules/Items/dtos/ItemErrorResponse.dto.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
/**
|
||||
* Item API Error Types
|
||||
* These error types are returned when item operations fail validation
|
||||
*/
|
||||
export enum ItemErrorType {
|
||||
/** Item name already exists in the system */
|
||||
ItemNameExists = 'ITEM_NAME_EXISTS',
|
||||
|
||||
/** Item category was not found */
|
||||
ItemCategoryNotFound = 'ITEM_CATEOGRY_NOT_FOUND',
|
||||
|
||||
/** Cost account is not a Cost of Goods Sold account */
|
||||
CostAccountNotCogs = 'COST_ACCOUNT_NOT_COGS',
|
||||
|
||||
/** Cost account was not found */
|
||||
CostAccountNotFound = 'COST_ACCOUNT_NOT_FOUMD',
|
||||
|
||||
/** Sell account was not found */
|
||||
SellAccountNotFound = 'SELL_ACCOUNT_NOT_FOUND',
|
||||
|
||||
/** Sell account is not an income account */
|
||||
SellAccountNotIncome = 'SELL_ACCOUNT_NOT_INCOME',
|
||||
|
||||
/** Inventory account was not found */
|
||||
InventoryAccountNotFound = 'INVENTORY_ACCOUNT_NOT_FOUND',
|
||||
|
||||
/** Account is not an inventory type account */
|
||||
InventoryAccountNotInventory = 'INVENTORY_ACCOUNT_NOT_INVENTORY',
|
||||
|
||||
/** Multiple items have associated transactions */
|
||||
ItemsHaveAssociatedTransactions = 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS',
|
||||
|
||||
/** Item has associated transactions (singular) */
|
||||
ItemHasAssociatedTransactions = 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
|
||||
|
||||
/** Item has associated inventory adjustments */
|
||||
ItemHasAssociatedInventoryAdjustment = 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
||||
|
||||
/** Cannot change item type to inventory */
|
||||
ItemCannotChangeInventoryType = 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE',
|
||||
|
||||
/** Cannot change type when item has transactions */
|
||||
TypeCannotChangeWithItemHasTransactions = 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
||||
|
||||
/** Inventory account cannot be modified */
|
||||
InventoryAccountCannotModified = 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
|
||||
|
||||
/** Purchase tax rate was not found */
|
||||
PurchaseTaxRateNotFound = 'PURCHASE_TAX_RATE_NOT_FOUND',
|
||||
|
||||
/** Sell tax rate was not found */
|
||||
SellTaxRateNotFound = 'SELL_TAX_RATE_NOT_FOUND',
|
||||
|
||||
/** Income account is required for sellable items */
|
||||
IncomeAccountRequiredWithSellableItem = 'INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM',
|
||||
|
||||
/** Cost account is required for purchasable items */
|
||||
CostAccountRequiredWithPurchasableItem = 'COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM',
|
||||
|
||||
/** Item not found */
|
||||
NotFound = 'NOT_FOUND',
|
||||
|
||||
/** Items not found */
|
||||
ItemsNotFound = 'ITEMS_NOT_FOUND',
|
||||
}
|
||||
|
||||
/**
|
||||
* Item API Error Response
|
||||
* Returned when an item operation fails
|
||||
*/
|
||||
export class ItemErrorResponseDto {
|
||||
@ApiProperty({
|
||||
description: 'HTTP status code',
|
||||
example: 400,
|
||||
})
|
||||
statusCode: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Error type identifier',
|
||||
enum: ItemErrorType,
|
||||
example: ItemErrorType.ItemNameExists,
|
||||
})
|
||||
type: ItemErrorType;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Human-readable error message',
|
||||
example: 'The item name is already exist.',
|
||||
required: false,
|
||||
nullable: true,
|
||||
})
|
||||
message: string | null;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Additional error payload data',
|
||||
required: false,
|
||||
nullable: true,
|
||||
})
|
||||
payload: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Item API Error Response Wrapper
|
||||
*/
|
||||
export class ItemApiErrorResponseDto {
|
||||
@ApiProperty({
|
||||
description: 'Array of error details',
|
||||
type: [ItemErrorResponseDto],
|
||||
})
|
||||
errors: ItemErrorResponseDto[];
|
||||
}
|
||||
@@ -2,10 +2,8 @@ import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { Job } from 'bullmq';
|
||||
import { ClsService, UseCls } from 'nestjs-cls';
|
||||
import { Process } from '@nestjs/bull';
|
||||
import {
|
||||
OrganizationBuildQueue,
|
||||
OrganizationBuildQueueJob,
|
||||
OrganizationBuildQueueJobPayload,
|
||||
} from '../Organization.types';
|
||||
import { BuildOrganizationService } from '../commands/BuildOrganization.service';
|
||||
@@ -22,7 +20,6 @@ export class OrganizationBuildProcessor extends WorkerHost {
|
||||
super();
|
||||
}
|
||||
|
||||
@Process(OrganizationBuildQueueJob)
|
||||
@UseCls()
|
||||
async process(job: Job<OrganizationBuildQueueJobPayload>) {
|
||||
console.log('Processing organization build job:', job.id);
|
||||
|
||||
@@ -22,6 +22,7 @@ const providers = [
|
||||
},
|
||||
migrations: {
|
||||
directory: configService.get('systemDatabase.migrationDir'),
|
||||
loadExtensions: ['.js'],
|
||||
},
|
||||
seeds: {
|
||||
directory: configService.get('systemDatabase.seedsDir'),
|
||||
|
||||
@@ -33,6 +33,7 @@ export const TenancyDatabaseProxyProvider = ClsModule.forFeatureAsync({
|
||||
},
|
||||
migrations: {
|
||||
directory: configService.get('tenantDatabase.migrationsDir'),
|
||||
loadExtensions: ['.js'],
|
||||
},
|
||||
seeds: {
|
||||
directory: configService.get('tenantDatabase.seedsDir'),
|
||||
|
||||
@@ -4,7 +4,12 @@ import styled from 'styled-components';
|
||||
import { DataTable } from '../Datatable';
|
||||
|
||||
export const ReportDataTable = styled(DataTable)`
|
||||
--x-table-no-results-border-color: #ddd;
|
||||
|
||||
.bp4-dark & {
|
||||
--x-table-no-results-border-color: var(--color-dark-gray5);
|
||||
}
|
||||
.table .tbody .tr.no-results:last-of-type .td {
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid var(--x-table-no-results-border-color);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
import preferencesMenu from '@/constants/preferencesMenu';
|
||||
import { PreferencesMenu } from '@/constants/preferencesMenu';
|
||||
import PreferencesSidebarContainer from './PreferencesSidebarContainer';
|
||||
|
||||
import '@/style/pages/Preferences/Sidebar.scss';
|
||||
@@ -15,7 +15,7 @@ export default function PreferencesSidebar() {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
const items = preferencesMenu.map((item) =>
|
||||
const items = PreferencesMenu.map((item) =>
|
||||
item.divider ? (
|
||||
<MenuDivider title={item.title} />
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export default [
|
||||
export const AllocateLandedCostType = [
|
||||
{ name: intl.get('bills'), value: 'Bill' },
|
||||
{ name: intl.get('expenses'), value: 'Expense' },
|
||||
];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
export default {
|
||||
export const App = {
|
||||
"app_name": "BigCapital",
|
||||
"app_version": "0.0.1 (build 12344)",
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export default [
|
||||
export const ContactsOptions = [
|
||||
{ name: intl.get('customer'), path: 'customers' },
|
||||
{ name: intl.get('vendor'), path: 'vendors' },
|
||||
];
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
VendorAction,
|
||||
} from './abilityOption';
|
||||
|
||||
export default [
|
||||
export const KeyboardShortcutsOptions = [
|
||||
{
|
||||
shortcut_key: 'Shift + I',
|
||||
description: intl.get('jump_to_the_invoices'),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
|
||||
export default [
|
||||
export const PreferencesMenu = [
|
||||
{
|
||||
text: <T id={'general'} />,
|
||||
disabled: false,
|
||||
@@ -13,10 +13,10 @@ export default [
|
||||
disabled: false,
|
||||
href: '/preferences/branding',
|
||||
},
|
||||
{
|
||||
text: 'Billing',
|
||||
href: '/preferences/billing',
|
||||
},
|
||||
// {
|
||||
// text: 'Billing',
|
||||
// href: '/preferences/billing',
|
||||
// },
|
||||
{
|
||||
text: <T id={'users'} />,
|
||||
href: '/preferences/users',
|
||||
@@ -63,11 +63,11 @@ export default [
|
||||
disabled: false,
|
||||
href: '/preferences/items',
|
||||
},
|
||||
{
|
||||
text: 'Integrations',
|
||||
disabled: false,
|
||||
href: '/preferences/integrations'
|
||||
},
|
||||
// {
|
||||
// text: 'Integrations',
|
||||
// disabled: false,
|
||||
// href: '/preferences/integrations'
|
||||
// },
|
||||
{
|
||||
text: 'API Keys',
|
||||
disabled: false,
|
||||
|
||||
@@ -8,6 +8,11 @@ import { If, AppToaster } from '@/components';
|
||||
import { NormalCell, BalanceCell, BankBalanceCell } from './components';
|
||||
import { transformTableStateToQuery, isBlank } from '@/utils';
|
||||
|
||||
export const DeleteAccountTypeError = {
|
||||
AccountPredefined: 'account_predefined',
|
||||
AccountHasAssociatedTransactions: 'account_has_associated_transactions',
|
||||
};
|
||||
|
||||
/**
|
||||
* Account name accessor.
|
||||
*/
|
||||
@@ -26,13 +31,13 @@ export const accountNameAccessor = (account) => {
|
||||
* Handle delete errors in bulk and singular.
|
||||
*/
|
||||
export const handleDeleteErrors = (errors) => {
|
||||
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
|
||||
if (errors.find((e) => e.type === DeleteAccountTypeError.AccountPredefined)) {
|
||||
AppToaster.show({
|
||||
message: intl.get('cannot_delete_predefined_accounts'),
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
}
|
||||
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
|
||||
if (errors.find((e) => e.type === DeleteAccountTypeError.AccountHasAssociatedTransactions)) {
|
||||
AppToaster.show({
|
||||
message: intl.get('cannot_delete_account_has_associated_transactions'),
|
||||
intent: Intent.DANGER,
|
||||
|
||||
@@ -16,7 +16,7 @@ import { FormattedMessage as T, If, FFormGroup, FSelect, FRadioGroup, FInputGrou
|
||||
import { handleStringChange } from '@/utils';
|
||||
import { FieldRequiredHint } from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import allocateLandedCostType from '@/constants/allocateLandedCostType';
|
||||
import { AllocateLandedCostType } from '@/constants/allocateLandedCostType';
|
||||
|
||||
import AllocateLandedCostFormBody from './AllocateLandedCostFormBody';
|
||||
import {
|
||||
@@ -81,7 +81,7 @@ export default function AllocateLandedCostFormFields() {
|
||||
>
|
||||
<FSelect
|
||||
name={'transaction_type'}
|
||||
items={allocateLandedCostType}
|
||||
items={AllocateLandedCostType}
|
||||
onItemChange={handleTransactionTypeChange}
|
||||
filterable={false}
|
||||
valueAccessor={'value'}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { FormattedMessage as T } from '@/components';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useContactDuplicateFromContext } from './ContactDuplicateProvider';
|
||||
|
||||
import Contacts from '@/constants/contactsOptions';
|
||||
import { ContactsOptions } from '@/constants/contactsOptions';
|
||||
|
||||
import { withDialogActions } from '@/containers/Dialog/withDialogActions';
|
||||
import { compose } from '@/utils';
|
||||
@@ -66,7 +66,7 @@ function ContactDuplicateForm({
|
||||
>
|
||||
<FSelect
|
||||
name={'contact_type'}
|
||||
items={Contacts}
|
||||
items={ContactsOptions}
|
||||
placeholder={<T id={'select_contact'} />}
|
||||
textAccessor={'name'}
|
||||
valueAccessor={'path'}
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { getColumnWidth } from '@/utils';
|
||||
import * as R from 'ramda';
|
||||
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
|
||||
import { Align } from '@/constants';
|
||||
import { Align, CLASSES } from '@/constants';
|
||||
|
||||
/**
|
||||
* Description cell – wraps value in a div with muted text class.
|
||||
*/
|
||||
function DescriptionCell({ cell: { value } }) {
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ className: `cell ${CLASSES.TEXT_MUTED}` },
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
|
||||
|
||||
@@ -75,6 +87,16 @@ const transactionIdColumnAccessor = (column) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Description column accessor (muted text in wrapped cell).
|
||||
*/
|
||||
const descriptionColumnAccessor = (column) => {
|
||||
return {
|
||||
...column,
|
||||
Cell: DescriptionCell,
|
||||
};
|
||||
};
|
||||
|
||||
const dynamiColumnMapper = R.curry((data, column) => {
|
||||
const _numericColumnAccessor = numericColumnAccessor(data);
|
||||
|
||||
@@ -88,6 +110,7 @@ const dynamiColumnMapper = R.curry((data, column) => {
|
||||
R.pathEq(['key'], 'reference_number'),
|
||||
transactionIdColumnAccessor,
|
||||
),
|
||||
R.when(R.pathEq(['key'], 'description'), descriptionColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'amount'), _numericColumnAccessor),
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
// @ts-nocheck
|
||||
import { Align } from '@/constants';
|
||||
import React from 'react';
|
||||
import { Align, CLASSES } from '@/constants';
|
||||
import { getColumnWidth } from '@/utils';
|
||||
import * as R from 'ramda';
|
||||
import { useJournalSheetContext } from './JournalProvider';
|
||||
|
||||
/**
|
||||
* Description cell – wraps value in a div with muted text class.
|
||||
*/
|
||||
function DescriptionCell({ cell: { value } }) {
|
||||
return React.createElement(
|
||||
'span',
|
||||
{ className: `cell ${CLASSES.TEXT_MUTED}` },
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
|
||||
|
||||
const getReportColWidth = (data, accessor, headerText) => {
|
||||
@@ -86,6 +98,16 @@ const accountCodeColumnAccessor = (column) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Description column accessor (muted text in wrapped cell).
|
||||
*/
|
||||
const descriptionColumnAccessor = (column) => {
|
||||
return {
|
||||
...column,
|
||||
Cell: DescriptionCell,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Dynamic column mapper.
|
||||
* @param {} data -
|
||||
@@ -105,6 +127,7 @@ const dynamicColumnMapper = R.curry((data, column) => {
|
||||
R.pathEq(['key'], 'transaction_number'),
|
||||
transactionNumberColumnAccessor,
|
||||
),
|
||||
R.when(R.pathEq(['key'], 'description'), descriptionColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'account_code'), accountCodeColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
|
||||
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
|
||||
@@ -113,7 +136,7 @@ const dynamicColumnMapper = R.curry((data, column) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Composes the fetched dynamic columns from the server to the columns to pass it
|
||||
* Composes the fetched dynamic columns from the server to the columns to pass it
|
||||
* to the table component.
|
||||
*/
|
||||
export const dynamicColumns = (columns, data) => {
|
||||
|
||||
@@ -14,6 +14,18 @@ import { useSettingsSelector } from '@/hooks/state';
|
||||
import { transformItemFormData } from './ItemForm.schema';
|
||||
import { useWatch } from '@/hooks/utils';
|
||||
|
||||
/**
|
||||
* Error types for item operations.
|
||||
*/
|
||||
export const ItemErrorType = {
|
||||
ItemNameExists: 'ITEM_NAME_EXISTS',
|
||||
InventoryAccountCannotModified: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
|
||||
TypeCannotChangeWithItemHasTransactions: 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
||||
ItemHasAssociatedTransactions: 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
|
||||
ItemHasAssociatedInventoryAdjustment: 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
||||
ItemHasAssociatedTransactionsPlural: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS',
|
||||
} as const;
|
||||
|
||||
const defaultInitialValues = {
|
||||
active: 1,
|
||||
name: '',
|
||||
@@ -74,7 +86,7 @@ export const transitionItemTypeKeyToLabel = (itemTypeKey) => {
|
||||
// handle delete errors.
|
||||
export const handleDeleteErrors = (errors) => {
|
||||
if (
|
||||
errors.find((error) => error.type === 'ITEM_HAS_ASSOCIATED_TRANSACTINS')
|
||||
errors.find((error) => error.type === ItemErrorType.ItemHasAssociatedTransactions)
|
||||
) {
|
||||
AppToaster.show({
|
||||
message: intl.get('the_item_has_associated_transactions'),
|
||||
@@ -84,7 +96,7 @@ export const handleDeleteErrors = (errors) => {
|
||||
|
||||
if (
|
||||
errors.find(
|
||||
(error) => error.type === 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
||||
(error) => error.type === ItemErrorType.ItemHasAssociatedInventoryAdjustment,
|
||||
)
|
||||
) {
|
||||
AppToaster.show({
|
||||
@@ -96,7 +108,7 @@ export const handleDeleteErrors = (errors) => {
|
||||
}
|
||||
if (
|
||||
errors.find(
|
||||
(error) => error.type === 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
||||
(error) => error.type === ItemErrorType.TypeCannotChangeWithItemHasTransactions,
|
||||
)
|
||||
) {
|
||||
AppToaster.show({
|
||||
@@ -107,7 +119,7 @@ export const handleDeleteErrors = (errors) => {
|
||||
});
|
||||
}
|
||||
if (
|
||||
errors.find((error) => error.type === 'ITEM_HAS_ASSOCIATED_TRANSACTIONS')
|
||||
errors.find((error) => error.type === ItemErrorType.ItemHasAssociatedTransactionsPlural)
|
||||
) {
|
||||
AppToaster.show({
|
||||
message: intl.get('item.error.you_could_not_delete_item_has_associated'),
|
||||
@@ -214,10 +226,10 @@ export const transformSubmitRequestErrors = (error) => {
|
||||
} = error;
|
||||
const fields = {};
|
||||
|
||||
if (errors.find((e) => e.type === 'ITEM.NAME.ALREADY.EXISTS')) {
|
||||
if (errors.find((e) => e.type === ItemErrorType.ItemNameExists)) {
|
||||
fields.name = intl.get('the_name_used_before');
|
||||
}
|
||||
if (errors.find((e) => e.type === 'INVENTORY_ACCOUNT_CANNOT_MODIFIED')) {
|
||||
if (errors.find((e) => e.type === ItemErrorType.InventoryAccountCannotModified)) {
|
||||
AppToaster.show({
|
||||
message: intl.get('cannot_change_item_inventory_account'),
|
||||
intent: Intent.DANGER,
|
||||
@@ -225,7 +237,7 @@ export const transformSubmitRequestErrors = (error) => {
|
||||
}
|
||||
if (
|
||||
errors.find(
|
||||
(e) => e.type === 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
||||
(e) => e.type === ItemErrorType.TypeCannotChangeWithItemHasTransactions,
|
||||
)
|
||||
) {
|
||||
AppToaster.show({
|
||||
|
||||
@@ -87,7 +87,7 @@ export function WarehousesGridItemBox({
|
||||
<WarehouseBoxRoot>
|
||||
<WarehouseHeader>
|
||||
<WarehouseTitle>
|
||||
{title} {primary && <Icon icon={'star-18dp'} iconSize={16} />}
|
||||
{title} {primary ? <Icon icon={'star-18dp'} iconSize={16} /> : null}
|
||||
</WarehouseTitle>
|
||||
<WarehouseCode>{code}</WarehouseCode>
|
||||
<WarehouseIcon>
|
||||
@@ -118,12 +118,21 @@ export const WarehousesList = styled.div`
|
||||
`;
|
||||
|
||||
export const WarehouseBoxRoot = styled.div`
|
||||
--x-box-border-color: #c8cad0;
|
||||
--x-box-background-color: #fff;
|
||||
--x-box-hover-border-color: #0153cc;
|
||||
|
||||
.bp4-dark & {
|
||||
--x-box-border-color: rgba(255, 255, 255, 0.2);
|
||||
--x-box-background-color: var(--color-dark-gray3);
|
||||
--x-box-hover-border-color: #0153cc;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #c8cad0;
|
||||
background: #fff;
|
||||
border: 1px solid var(--x-box-border-color);
|
||||
background: var(--x-box-background-color);
|
||||
margin: 5px 5px 8px;
|
||||
width: 200px;
|
||||
height: 160px;
|
||||
@@ -132,7 +141,7 @@ export const WarehouseBoxRoot = styled.div`
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
border-color: #0153cc;
|
||||
border-color: var(--x-box-hover-border-color);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -143,9 +152,16 @@ export const WarehouseHeader = styled.div`
|
||||
`;
|
||||
|
||||
export const WarehouseTitle = styled.div`
|
||||
--x-title-color: #000;
|
||||
--x-title-icon-color: #e1b31d;
|
||||
|
||||
.bp4-dark & {
|
||||
--x-title-color: var(--color-light-gray5);
|
||||
--x-title-icon-color: #e1b31d;
|
||||
}
|
||||
font-size: 14px;
|
||||
font-style: inherit;
|
||||
color: #000;
|
||||
color: var(--x-title-color);
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
@@ -154,14 +170,19 @@ export const WarehouseTitle = styled.div`
|
||||
margin: 0;
|
||||
margin-left: 2px;
|
||||
vertical-align: top;
|
||||
color: #e1b31d;
|
||||
color: var(--x-title-icon-color);
|
||||
}
|
||||
`;
|
||||
|
||||
const WarehouseCode = styled.div`
|
||||
--x-code-color: #6b7176;
|
||||
|
||||
.bp4-dark & {
|
||||
--x-code-color: var(--color-muted-text);
|
||||
}
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #6b7176;
|
||||
color: var(--x-code-color);
|
||||
margin-top: 4px;
|
||||
`;
|
||||
|
||||
@@ -178,8 +199,13 @@ const WarehouseContent = styled.div`
|
||||
`;
|
||||
|
||||
const WarehouseItem = styled.div`
|
||||
--x-item-color: #000;
|
||||
|
||||
.bp4-dark & {
|
||||
--x-item-color: var(--color-light-gray1);
|
||||
}
|
||||
font-size: 11px;
|
||||
color: #000;
|
||||
color: var(--x-item-color);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import keyboardShortcuts from '@/constants/keyboardShortcutsOptions';
|
||||
import { KeyboardShortcutsOptions } from '@/constants/keyboardShortcutsOptions';
|
||||
import { useAbilitiesFilter } from '../utils/useAbilityContext';
|
||||
|
||||
/**
|
||||
@@ -10,7 +10,7 @@ export const useKeywordShortcuts = () => {
|
||||
const abilitiesFilter = useAbilitiesFilter();
|
||||
|
||||
return React.useMemo(
|
||||
() => abilitiesFilter(keyboardShortcuts),
|
||||
() => abilitiesFilter(KeyboardShortcutsOptions),
|
||||
[abilitiesFilter],
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useQuery } from 'react-query';
|
||||
import { castArray, defaultTo } from 'lodash';
|
||||
import { useAuthOrganizationId } from './state';
|
||||
import useApiRequest from './useRequest';
|
||||
import { normalizeApiPath } from '../utils';
|
||||
import { useRef } from 'react';
|
||||
|
||||
/**
|
||||
@@ -19,7 +20,11 @@ export function useRequestQuery(query, axios, props) {
|
||||
|
||||
const states = useQuery(
|
||||
query,
|
||||
() => apiRequest.http({ ...axios, url: `/api/${axios.url}` }),
|
||||
() =>
|
||||
apiRequest.http({
|
||||
...axios,
|
||||
url: `/api/${normalizeApiPath(axios.url)}`,
|
||||
}),
|
||||
props,
|
||||
);
|
||||
// Momerize the default data.
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useSetGlobalErrors,
|
||||
useAuthToken,
|
||||
} from './state';
|
||||
import { getCookie } from '../utils';
|
||||
import { getCookie, normalizeApiPath } from '../utils';
|
||||
|
||||
export default function useApiRequest() {
|
||||
const setGlobalErrors = useSetGlobalErrors();
|
||||
@@ -93,27 +93,27 @@ export default function useApiRequest() {
|
||||
http,
|
||||
|
||||
get(resource, params) {
|
||||
return http.get(`/api/${resource}`, params);
|
||||
return http.get(`/api/${normalizeApiPath(resource)}`, params);
|
||||
},
|
||||
|
||||
post(resource, params, config) {
|
||||
return http.post(`/api/${resource}`, params, config);
|
||||
return http.post(`/api/${normalizeApiPath(resource)}`, params, config);
|
||||
},
|
||||
|
||||
update(resource, slug, params) {
|
||||
return http.put(`/api/${resource}/${slug}`, params);
|
||||
return http.put(`/api/${normalizeApiPath(resource)}/${slug}`, params);
|
||||
},
|
||||
|
||||
put(resource, params) {
|
||||
return http.put(`/api/${resource}`, params);
|
||||
return http.put(`/api/${normalizeApiPath(resource)}`, params);
|
||||
},
|
||||
|
||||
patch(resource, params, config) {
|
||||
return http.patch(`/api/${resource}`, params, config);
|
||||
return http.patch(`/api/${normalizeApiPath(resource)}`, params, config);
|
||||
},
|
||||
|
||||
delete(resource, params) {
|
||||
return http.delete(`/api/${resource}`, params);
|
||||
return http.delete(`/api/${normalizeApiPath(resource)}`, params);
|
||||
},
|
||||
}),
|
||||
[http],
|
||||
@@ -130,22 +130,22 @@ export function useAuthApiRequest() {
|
||||
() => ({
|
||||
http,
|
||||
get(resource, params) {
|
||||
return http.get(`/api/${resource}`, params);
|
||||
return http.get(`/api/${normalizeApiPath(resource)}`, params);
|
||||
},
|
||||
post(resource, params, config) {
|
||||
return http.post(`/api/${resource}`, params, config);
|
||||
return http.post(`/api/${normalizeApiPath(resource)}`, params, config);
|
||||
},
|
||||
update(resource, slug, params) {
|
||||
return http.put(`/api/${resource}/${slug}`, params);
|
||||
return http.put(`/api/${normalizeApiPath(resource)}/${slug}`, params);
|
||||
},
|
||||
put(resource, params) {
|
||||
return http.put(`/api/${resource}`, params);
|
||||
return http.put(`/api/${normalizeApiPath(resource)}`, params);
|
||||
},
|
||||
patch(resource, params, config) {
|
||||
return http.patch(`/api/${resource}`, params, config);
|
||||
return http.patch(`/api/${normalizeApiPath(resource)}`, params, config);
|
||||
},
|
||||
delete(resource, params) {
|
||||
return http.delete(`/api/${resource}`, params);
|
||||
return http.delete(`/api/${normalizeApiPath(resource)}`, params);
|
||||
},
|
||||
}),
|
||||
[http],
|
||||
|
||||
@@ -11,7 +11,10 @@ export const getPreferenceRoutes = () => [
|
||||
},
|
||||
{
|
||||
path: `${BASE_URL}/branding`,
|
||||
component: lazy(() => import('../containers/Preferences/Branding/PreferencesBrandingPage')),
|
||||
component: lazy(
|
||||
() =>
|
||||
import('../containers/Preferences/Branding/PreferencesBrandingPage'),
|
||||
),
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
@@ -29,14 +32,20 @@ export const getPreferenceRoutes = () => [
|
||||
{
|
||||
path: `${BASE_URL}/payment-methods`,
|
||||
component: lazy(
|
||||
() => import('../containers/Preferences/PaymentMethods/PreferencesPaymentMethodsPage'),
|
||||
() =>
|
||||
import(
|
||||
'../containers/Preferences/PaymentMethods/PreferencesPaymentMethodsPage'
|
||||
),
|
||||
),
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
path: `${BASE_URL}/payment-methods/stripe/callback`,
|
||||
component: lazy(
|
||||
() => import('../containers/Preferences/PaymentMethods/PreferencesStripeCallback'),
|
||||
() =>
|
||||
import(
|
||||
'../containers/Preferences/PaymentMethods/PreferencesStripeCallback'
|
||||
),
|
||||
),
|
||||
exact: true,
|
||||
},
|
||||
@@ -112,16 +121,6 @@ export const getPreferenceRoutes = () => [
|
||||
component: lazy(() => import('@/containers/Preferences/Item')),
|
||||
exact: true,
|
||||
},
|
||||
// {
|
||||
// path: `${BASE_URL}/sms-message`,
|
||||
// component: SMSIntegration,
|
||||
// exact: true,
|
||||
// },
|
||||
{
|
||||
path: `${BASE_URL}/billing`,
|
||||
component: lazy(() => import('@/containers/Subscriptions/BillingPage')),
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
path: `${BASE_URL}/api-keys`,
|
||||
component: lazy(() => import('@/containers/Preferences/ApiKeys/ApiKeys')),
|
||||
|
||||
@@ -196,7 +196,6 @@ $ns: bp4;
|
||||
--color-preferences-sidebar-head-border: #bbcbd0;
|
||||
--color-preferences-sidebar-head-text: #3b3b4c;
|
||||
|
||||
|
||||
// Preferences - Topbar.
|
||||
--color-preferences-topbar-background: #fff;
|
||||
--color-preferences-topbar-border: #d2dde2;
|
||||
@@ -209,7 +208,7 @@ $ns: bp4;
|
||||
--color-financial-sheet-title-text: rgb(31, 50, 85);
|
||||
--color-financial-sheet-type-text: rgb(31, 50, 85);
|
||||
--color-financial-sheet-date-text: rgb(31, 50, 85);
|
||||
--color-financial-sheet-footer-text: rgb(31, 50, 85);
|
||||
--color-financial-sheet-footer-text: var(--color-muted-text);
|
||||
--color-financial-sheet-minimal-title-text: #333;
|
||||
|
||||
// Transaction locking.
|
||||
@@ -514,7 +513,7 @@ body.bp4-dark {
|
||||
--color-financial-sheet-title-text: var(--color-light-gray1);
|
||||
--color-financial-sheet-type-text: var(--color-light-gray1);
|
||||
--color-financial-sheet-date-text: var(--color-light-gray1);
|
||||
--color-financial-sheet-footer-text: var(--color-light-gray1);
|
||||
--color-financial-sheet-footer-text: var(--color-muted-text);
|
||||
--color-financial-sheet-minimal-title-text: var(--color-white);
|
||||
|
||||
// Transaction locking.
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
border-bottom: 1px solid var(--color-datatable-constrant-head-border);
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
.tbody .tr .td {
|
||||
background: transparent;
|
||||
padding: 0.5rem 0.5rem;
|
||||
@@ -375,3 +375,32 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sticky header: blurred transparent background so body rows don't show through
|
||||
.bigcapital-datatable.has-sticky,
|
||||
.bigcapital-datatable.has-sticky-header {
|
||||
&.table-constrant .table .thead .th,
|
||||
&.table--constrant .table .thead .th {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
|
||||
body.bp4-dark & {
|
||||
background: rgba(28, 33, 39, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sticky cells in table body: blurred transparent background so content doesn't show through
|
||||
.bigcapital-datatable.has-sticky {
|
||||
&.table-constrant .table .tbody .tr:not(:hover) .td[data-sticky-td],
|
||||
&.table--constrant .table .tbody .tr:not(:hover) .td[data-sticky-td] {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
|
||||
body.bp4-dark & {
|
||||
background: rgba(28, 33, 39, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,9 @@ import jsCookie from 'js-cookie';
|
||||
import { deepMapKeys } from './map-key-deep';
|
||||
export * from './deep';
|
||||
|
||||
/** Strips leading slash from a path segment to avoid double slashes when joining with a base (e.g. `/api/` + path). */
|
||||
export const normalizeApiPath = (path) => (path || '').replace(/^\//, '');
|
||||
|
||||
export const getCookie = (name, defaultValue) =>
|
||||
_.defaultTo(jsCookie.get(name), defaultValue);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user