diff --git a/packages/server/src/i18n/en/bill.json b/packages/server/src/i18n/en/bill.json index ab3740a07..92ee0a933 100644 --- a/packages/server/src/i18n/en/bill.json +++ b/packages/server/src/i18n/en/bill.json @@ -22,6 +22,9 @@ "field.status.unpaid": "Unpaid", "field.status.opened": "Opened", "field.status.draft": "Draft", - "field.created_at": "Created At" + "field.created_at": "Created At", + "allocation_method": "Allocation Method", + "allocation_method.quantity": "Quantity", + "allocation_method.value": "Valuation" } diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts index 1cd28fd0f..e4ebaebd3 100644 --- a/packages/server/src/main.ts +++ b/packages/server/src/main.ts @@ -4,10 +4,6 @@ import { ClsMiddleware } from 'nestjs-cls'; import * as path from 'path'; import './utils/moment-mysql'; import { AppModule } from './modules/App/App.module'; -import { ServiceErrorFilter } from './common/filters/service-error.filter'; -import { ModelHasRelationsFilter } from './common/filters/model-has-relations.filter'; -import { ValidationPipe } from './common/pipes/ClassValidation.pipe'; -import { ToJsonInterceptor } from './common/interceptors/to-json.interceptor'; import { NestExpressApplication } from '@nestjs/platform-express'; global.__public_dirname = path.join(__dirname, '..', 'public'); @@ -25,11 +21,6 @@ async function bootstrap() { // create and mount the middleware manually here app.use(new ClsMiddleware({}).use); - app.useGlobalInterceptors(new ToJsonInterceptor()); - - // use the validation pipe globally - app.useGlobalPipes(new ValidationPipe()); - const config = new DocumentBuilder() .setTitle('Bigcapital') .setDescription('Financial accounting software') @@ -39,9 +30,6 @@ async function bootstrap() { const documentFactory = () => SwaggerModule.createDocument(app, config); SwaggerModule.setup('swagger', app, documentFactory); - app.useGlobalFilters(new ServiceErrorFilter()); - app.useGlobalFilters(new ModelHasRelationsFilter()); - await app.listen(process.env.PORT ?? 3000); } bootstrap(); diff --git a/packages/server/src/modules/App/App.module.ts b/packages/server/src/modules/App/App.module.ts index 98d69d3f0..ded1ffda5 100644 --- a/packages/server/src/modules/App/App.module.ts +++ b/packages/server/src/modules/App/App.module.ts @@ -1,7 +1,7 @@ import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { EventEmitterModule } from '@nestjs/event-emitter'; -import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; +import { APP_GUARD, APP_INTERCEPTOR, APP_PIPE, APP_FILTER } from '@nestjs/core'; import { join } from 'path'; import { ServeStaticModule } from '@nestjs/serve-static'; import { RedisModule } from '@liaoliaots/nestjs-redis'; @@ -36,6 +36,10 @@ import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module'; import { BranchesModule } from '../Branches/Branches.module'; import { WarehousesModule } from '../Warehouses/Warehouses.module'; import { SerializeInterceptor } from '@/common/interceptors/serialize.interceptor'; +import { ValidationPipe } from '@/common/pipes/ClassValidation.pipe'; +import { ToJsonInterceptor } from '@/common/interceptors/to-json.interceptor'; +import { ServiceErrorFilter } from '@/common/filters/service-error.filter'; +import { ModelHasRelationsFilter } from '@/common/filters/model-has-relations.filter'; import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module'; import { CustomersModule } from '../Customers/Customers.module'; import { VendorsModule } from '../Vendors/Vendors.module'; @@ -234,10 +238,18 @@ import { AppThrottleModule } from './AppThrottle.module'; ], controllers: [AppController], providers: [ + { + provide: APP_PIPE, + useClass: ValidationPipe, + }, { provide: APP_GUARD, useClass: ThrottlerGuard, }, + { + provide: APP_INTERCEPTOR, + useClass: ToJsonInterceptor, + }, { provide: APP_INTERCEPTOR, useClass: SerializeInterceptor, @@ -250,6 +262,14 @@ import { AppThrottleModule } from './AppThrottle.module'; provide: APP_INTERCEPTOR, useClass: ExcludeNullInterceptor, }, + { + provide: APP_FILTER, + useClass: ServiceErrorFilter, + }, + { + provide: APP_FILTER, + useClass: ModelHasRelationsFilter, + }, AppService, ], }) diff --git a/packages/server/src/modules/BankingTransactionsExclude/commands/UnexcludeBankTransaction.service.ts b/packages/server/src/modules/BankingTransactionsExclude/commands/UnexcludeBankTransaction.service.ts index 86e5f4525..3a3ec6d75 100644 --- a/packages/server/src/modules/BankingTransactionsExclude/commands/UnexcludeBankTransaction.service.ts +++ b/packages/server/src/modules/BankingTransactionsExclude/commands/UnexcludeBankTransaction.service.ts @@ -4,8 +4,8 @@ import { validateTransactionShouldBeExcluded, } from './utils'; import { - IBankTransactionExcludedEventPayload, - IBankTransactionExcludingEventPayload, + IBankTransactionUnexcludedEventPayload, + IBankTransactionUnexcludingEventPayload, } from '../types/BankTransactionsExclude.types'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { Inject, Injectable } from '@nestjs/common'; @@ -24,7 +24,7 @@ export class UnexcludeBankTransactionService { private readonly uncategorizedBankTransactionModel: TenantModelProxy< typeof UncategorizedBankTransaction >, - ) {} + ) { } /** * Marks the given bank transaction as excluded. @@ -50,7 +50,8 @@ export class UnexcludeBankTransactionService { return this.uow.withTransaction(async (trx: Knex.Transaction) => { await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluding, { uncategorizedTransactionId, - } as IBankTransactionExcludingEventPayload); + trx, + } as IBankTransactionUnexcludingEventPayload); await this.uncategorizedBankTransactionModel() .query(trx) @@ -61,7 +62,8 @@ export class UnexcludeBankTransactionService { await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluded, { uncategorizedTransactionId, - } as IBankTransactionExcludedEventPayload); + trx, + } as IBankTransactionUnexcludedEventPayload); }); } } diff --git a/packages/server/src/modules/BankingTransactionsExclude/subscribers/DecrementUncategorizedTransactionOnExclude.ts b/packages/server/src/modules/BankingTransactionsExclude/subscribers/DecrementUncategorizedTransactionOnExclude.ts index ce56b324f..fc97d259a 100644 --- a/packages/server/src/modules/BankingTransactionsExclude/subscribers/DecrementUncategorizedTransactionOnExclude.ts +++ b/packages/server/src/modules/BankingTransactionsExclude/subscribers/DecrementUncategorizedTransactionOnExclude.ts @@ -19,7 +19,7 @@ export class DecrementUncategorizedTransactionOnExclude { private readonly uncategorizedBankTransaction: TenantModelProxy< typeof UncategorizedBankTransaction >, - ) {} + ) { } /** * Validates the cashflow transaction whether matched with bank transaction on deleting. @@ -50,7 +50,7 @@ export class DecrementUncategorizedTransactionOnExclude { trx, }: IBankTransactionUnexcludedEventPayload) { const transaction = await this.uncategorizedBankTransaction() - .query() + .query(trx) .findById(uncategorizedTransactionId); // await this.account() diff --git a/packages/server/src/modules/BillLandedCosts/LandedCost.controller.ts b/packages/server/src/modules/BillLandedCosts/LandedCost.controller.ts index 6ed7ed8a5..bed8535b0 100644 --- a/packages/server/src/modules/BillLandedCosts/LandedCost.controller.ts +++ b/packages/server/src/modules/BillLandedCosts/LandedCost.controller.ts @@ -25,7 +25,7 @@ export class BillAllocateLandedCostController { private billAllocatedCostTransactions: BillAllocatedLandedCostTransactions, private revertAllocatedLandedCost: RevertAllocatedLandedCost, private landedCostTransactions: LandedCostTranasctions, - ) {} + ) { } @Get('/transactions') @ApiOperation({ summary: 'Get landed cost transactions' }) diff --git a/packages/server/src/modules/BillLandedCosts/commands/BillAllocatedLandedCostTransactions.service.ts b/packages/server/src/modules/BillLandedCosts/commands/BillAllocatedLandedCostTransactions.service.ts index 49764b6a8..a50af6284 100644 --- a/packages/server/src/modules/BillLandedCosts/commands/BillAllocatedLandedCostTransactions.service.ts +++ b/packages/server/src/modules/BillLandedCosts/commands/BillAllocatedLandedCostTransactions.service.ts @@ -21,7 +21,7 @@ export class BillAllocatedLandedCostTransactions { private readonly billLandedCostModel: TenantModelProxy< typeof BillLandedCost >, - ) {} + ) { } /** * Retrieve the bill associated landed cost transactions. @@ -77,6 +77,13 @@ export class BillAllocatedLandedCostTransactions { transaction.fromTransactionType, transaction, ); + const allocationMethodFormattedKey = transaction.allocationMethodFormatted; + const allocationMethodFormatted = allocationMethodFormattedKey + ? this.i18nService.t(allocationMethodFormattedKey, { + defaultValue: allocationMethodFormattedKey, + }) + : ''; + return { formattedAmount: formatNumber(transaction.amount, { currencyCode: transaction.currencyCode, @@ -84,12 +91,14 @@ export class BillAllocatedLandedCostTransactions { ...omit(transaction, [ 'allocatedFromBillEntry', 'allocatedFromExpenseEntry', + 'allocationMethodFormatted', ]), name, description, formattedLocalAmount: formatNumber(transaction.localAmount, { currencyCode: 'USD', }), + allocationMethodFormatted, }; }; diff --git a/packages/server/src/modules/BillLandedCosts/commands/LandedCostTransactions.service.ts b/packages/server/src/modules/BillLandedCosts/commands/LandedCostTransactions.service.ts index c15c83da5..6b866779c 100644 --- a/packages/server/src/modules/BillLandedCosts/commands/LandedCostTransactions.service.ts +++ b/packages/server/src/modules/BillLandedCosts/commands/LandedCostTransactions.service.ts @@ -14,7 +14,7 @@ import { LandedCostTransactionsQueryDto } from '../dtos/LandedCostTransactionsQu @Injectable() export class LandedCostTranasctions { - constructor(private readonly transactionLandedCost: TransactionLandedCost) {} + constructor(private readonly transactionLandedCost: TransactionLandedCost) { } /** * Retrieve the landed costs based on the given query. @@ -45,8 +45,8 @@ export class LandedCostTranasctions { )(transactionType); return pipe( - this.transformLandedCostTransactions, R.map(transformLandedCost), + this.transformLandedCostTransactions, )(transactions); }; @@ -90,7 +90,7 @@ export class LandedCostTranasctions { const entries = R.map< ILandedCostTransactionEntry, ILandedCostTransactionEntryDOJO - >(transformLandedCostEntry)(transaction.entries); + >(transformLandedCostEntry)(transaction.entries ?? []); return { ...transaction, diff --git a/packages/server/src/modules/BillLandedCosts/dtos/AllocateBillLandedCost.dto.ts b/packages/server/src/modules/BillLandedCosts/dtos/AllocateBillLandedCost.dto.ts index 063157e96..2cc4f833d 100644 --- a/packages/server/src/modules/BillLandedCosts/dtos/AllocateBillLandedCost.dto.ts +++ b/packages/server/src/modules/BillLandedCosts/dtos/AllocateBillLandedCost.dto.ts @@ -4,7 +4,6 @@ import { IsOptional, IsArray, ValidateNested, - IsDecimal, IsString, IsNumber, } from 'class-validator'; @@ -17,8 +16,9 @@ export class AllocateBillLandedCostItemDto { @ToNumber() entryId: number; - @IsDecimal() - cost: string; // Use string for IsDecimal, or use @IsNumber() if you want a number + @IsNumber() + @ToNumber() + cost: number; } export class AllocateBillLandedCostDto { diff --git a/packages/server/src/modules/BillLandedCosts/models/BillLandedCost.ts b/packages/server/src/modules/BillLandedCosts/models/BillLandedCost.ts index 4cf568cef..80347fe7e 100644 --- a/packages/server/src/modules/BillLandedCosts/models/BillLandedCost.ts +++ b/packages/server/src/modules/BillLandedCosts/models/BillLandedCost.ts @@ -60,8 +60,8 @@ export class BillLandedCost extends BaseModel { const allocationMethod = lowerCase(this.allocationMethod); const keyLabelsPairs = { - value: 'allocation_method.value.label', - quantity: 'allocation_method.quantity.label', + value: 'bill.allocation_method.value', + quantity: 'bill.allocation_method.quantity', }; return keyLabelsPairs[allocationMethod] || ''; } diff --git a/packages/webapp/src/components/Datatable/Pagination.tsx b/packages/webapp/src/components/Datatable/Pagination.tsx index 2dd1186ba..2bd10a643 100644 --- a/packages/webapp/src/components/Datatable/Pagination.tsx +++ b/packages/webapp/src/components/Datatable/Pagination.tsx @@ -1,14 +1,115 @@ // @ts-nocheck import React, { useReducer, useEffect } from 'react'; -import classNames from 'classnames'; import { Button, ButtonGroup, Intent, HTMLSelect } from '@blueprintjs/core'; -import { FormattedMessage as T } from '@/components'; import intl from 'react-intl-universal'; import PropTypes from 'prop-types'; import { range } from 'lodash'; -import { Icon } from '@/components'; +import styled from 'styled-components'; +import { x } from '@xstyled/emotion'; +import { Icon, FormattedMessage as T } from '@/components'; +import { useIsDarkMode } from '@/hooks/useDarkMode'; -import '@/style/components/DataTable/Pagination.scss'; +// Styled components +const StyledButtonGroup = styled(ButtonGroup)` + .bp4-button { + background: transparent; + padding: 5px; + } +`; + +const StyledPaginationButton = styled(Button)` + --x-button-text-color: #666666; + --x-button-hover-background: #E6EFFB; + --x-button-active-text-color: #000; + --x-button-active-background: #E6EFFB; + --x-button-active-disabled-background: #E6EFFB; + + .bp4-dark & { + --x-button-text-color: rgba(255, 255, 255, 0.8); + --x-button-hover-background: var(--color-dark-gray3); + --x-button-active-text-color: var(--color-light-gray2); + --x-button-active-background: var(--color-dark-gray3); + --x-button-active-disabled-background: var(--color-dark-gray3); + } + min-width: 24px; + min-height: 24px; + border-radius: 5px; + + &:not([class*="bp4-intent-"]).bp4-minimal { + color: var(--x-button-text-color); + + &:hover { + background-color: var(--x-button-hover-background); + } + .bp4-icon { + margin-right: 4px; + color: var(--x-button-text-color); + } + } + &.is-active { + &.bp4-intent-primary.bp4-minimal:disabled, + &.bp4-intent-primary.bp4-minimal.bp4-disabled { + background-color: var(--x-button-active-disabled-background); + + .bp4-button-text { + color: var(--x-button-active-text-color); + } + } + } +`; + +const StyledPreviousButton = styled(StyledPaginationButton)` + padding-left: 10px; + padding-right: 10px; + + .bp4-icon { + [dir="rtl"] & { + transform: scale(-1); + } + } +`; + +const StyledNextButton = styled(StyledPaginationButton)` + padding-left: 10px; + padding-right: 10px; + + .bp4-icon { + order: 1; + margin-right: 0; + margin-left: 4px; + } +`; + +const StyledHTMLSelect = styled(HTMLSelect)` + --x-html-select-text-color: #666; + --x-html-select-border-color: #e8e8e8; + + .bp4-dark & { + --x-html-select-text-color: rgba(255, 255, 255, 0.8); + --x-html-select-border-color: rgba(255, 255, 255, 0.15); + } + &.bp4-html-select.bp4-minimal { + margin-left: 6px; + + select { + height: 24px; + width: auto; + padding: 0; + padding-right: 14px; + padding-left: 8px; + border: 1px solid var(--x-html-select-border-color); + font-size: 13px; + border-radius: 3px; + color: var(--x-html-select-text-color); + } + &::after { + border-left-width: 3px; + border-right-width: 3px; + border-top-width: 4px; + margin-right: 6px; + } + } +`; const TYPE = { PAGE_CHANGE: 'PAGE_CHANGE', @@ -91,6 +192,7 @@ export function Pagination({ onPageChange, onPageSizeChange, }) { + const isDark = useIsDarkMode(); const [state, dispatch] = useReducer( reducer, { currentPage, total, size }, @@ -107,10 +209,10 @@ export function Pagination({ }, [total, size, currentPage]); return ( -