Merge branch 'develop' into more-e2e-test-cases

This commit is contained in:
Ahmed Bouhuolia
2026-01-18 15:01:49 +02:00
22 changed files with 248 additions and 98 deletions

View File

@@ -22,6 +22,9 @@
"field.status.unpaid": "Unpaid", "field.status.unpaid": "Unpaid",
"field.status.opened": "Opened", "field.status.opened": "Opened",
"field.status.draft": "Draft", "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"
} }

View File

@@ -4,10 +4,6 @@ import { ClsMiddleware } from 'nestjs-cls';
import * as path from 'path'; import * as path from 'path';
import './utils/moment-mysql'; import './utils/moment-mysql';
import { AppModule } from './modules/App/App.module'; 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'; import { NestExpressApplication } from '@nestjs/platform-express';
global.__public_dirname = path.join(__dirname, '..', 'public'); global.__public_dirname = path.join(__dirname, '..', 'public');
@@ -25,11 +21,6 @@ async function bootstrap() {
// create and mount the middleware manually here // create and mount the middleware manually here
app.use(new ClsMiddleware({}).use); app.use(new ClsMiddleware({}).use);
app.useGlobalInterceptors(new ToJsonInterceptor());
// use the validation pipe globally
app.useGlobalPipes(new ValidationPipe());
const config = new DocumentBuilder() const config = new DocumentBuilder()
.setTitle('Bigcapital') .setTitle('Bigcapital')
.setDescription('Financial accounting software') .setDescription('Financial accounting software')
@@ -39,9 +30,6 @@ async function bootstrap() {
const documentFactory = () => SwaggerModule.createDocument(app, config); const documentFactory = () => SwaggerModule.createDocument(app, config);
SwaggerModule.setup('swagger', app, documentFactory); SwaggerModule.setup('swagger', app, documentFactory);
app.useGlobalFilters(new ServiceErrorFilter());
app.useGlobalFilters(new ModelHasRelationsFilter());
await app.listen(process.env.PORT ?? 3000); await app.listen(process.env.PORT ?? 3000);
} }
bootstrap(); bootstrap();

View File

@@ -1,7 +1,7 @@
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common'; import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter'; 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 { join } from 'path';
import { ServeStaticModule } from '@nestjs/serve-static'; import { ServeStaticModule } from '@nestjs/serve-static';
import { RedisModule } from '@liaoliaots/nestjs-redis'; import { RedisModule } from '@liaoliaots/nestjs-redis';
@@ -36,6 +36,10 @@ import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
import { BranchesModule } from '../Branches/Branches.module'; import { BranchesModule } from '../Branches/Branches.module';
import { WarehousesModule } from '../Warehouses/Warehouses.module'; import { WarehousesModule } from '../Warehouses/Warehouses.module';
import { SerializeInterceptor } from '@/common/interceptors/serialize.interceptor'; 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 { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
import { CustomersModule } from '../Customers/Customers.module'; import { CustomersModule } from '../Customers/Customers.module';
import { VendorsModule } from '../Vendors/Vendors.module'; import { VendorsModule } from '../Vendors/Vendors.module';
@@ -234,10 +238,18 @@ import { AppThrottleModule } from './AppThrottle.module';
], ],
controllers: [AppController], controllers: [AppController],
providers: [ providers: [
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
{ {
provide: APP_GUARD, provide: APP_GUARD,
useClass: ThrottlerGuard, useClass: ThrottlerGuard,
}, },
{
provide: APP_INTERCEPTOR,
useClass: ToJsonInterceptor,
},
{ {
provide: APP_INTERCEPTOR, provide: APP_INTERCEPTOR,
useClass: SerializeInterceptor, useClass: SerializeInterceptor,
@@ -250,6 +262,14 @@ import { AppThrottleModule } from './AppThrottle.module';
provide: APP_INTERCEPTOR, provide: APP_INTERCEPTOR,
useClass: ExcludeNullInterceptor, useClass: ExcludeNullInterceptor,
}, },
{
provide: APP_FILTER,
useClass: ServiceErrorFilter,
},
{
provide: APP_FILTER,
useClass: ModelHasRelationsFilter,
},
AppService, AppService,
], ],
}) })

View File

@@ -4,8 +4,8 @@ import {
validateTransactionShouldBeExcluded, validateTransactionShouldBeExcluded,
} from './utils'; } from './utils';
import { import {
IBankTransactionExcludedEventPayload, IBankTransactionUnexcludedEventPayload,
IBankTransactionExcludingEventPayload, IBankTransactionUnexcludingEventPayload,
} from '../types/BankTransactionsExclude.types'; } from '../types/BankTransactionsExclude.types';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
@@ -24,7 +24,7 @@ export class UnexcludeBankTransactionService {
private readonly uncategorizedBankTransactionModel: TenantModelProxy< private readonly uncategorizedBankTransactionModel: TenantModelProxy<
typeof UncategorizedBankTransaction typeof UncategorizedBankTransaction
>, >,
) {} ) { }
/** /**
* Marks the given bank transaction as excluded. * Marks the given bank transaction as excluded.
@@ -50,7 +50,8 @@ export class UnexcludeBankTransactionService {
return this.uow.withTransaction(async (trx: Knex.Transaction) => { return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluding, { await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluding, {
uncategorizedTransactionId, uncategorizedTransactionId,
} as IBankTransactionExcludingEventPayload); trx,
} as IBankTransactionUnexcludingEventPayload);
await this.uncategorizedBankTransactionModel() await this.uncategorizedBankTransactionModel()
.query(trx) .query(trx)
@@ -61,7 +62,8 @@ export class UnexcludeBankTransactionService {
await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluded, { await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluded, {
uncategorizedTransactionId, uncategorizedTransactionId,
} as IBankTransactionExcludedEventPayload); trx,
} as IBankTransactionUnexcludedEventPayload);
}); });
} }
} }

View File

@@ -19,7 +19,7 @@ export class DecrementUncategorizedTransactionOnExclude {
private readonly uncategorizedBankTransaction: TenantModelProxy< private readonly uncategorizedBankTransaction: TenantModelProxy<
typeof UncategorizedBankTransaction typeof UncategorizedBankTransaction
>, >,
) {} ) { }
/** /**
* Validates the cashflow transaction whether matched with bank transaction on deleting. * Validates the cashflow transaction whether matched with bank transaction on deleting.
@@ -50,7 +50,7 @@ export class DecrementUncategorizedTransactionOnExclude {
trx, trx,
}: IBankTransactionUnexcludedEventPayload) { }: IBankTransactionUnexcludedEventPayload) {
const transaction = await this.uncategorizedBankTransaction() const transaction = await this.uncategorizedBankTransaction()
.query() .query(trx)
.findById(uncategorizedTransactionId); .findById(uncategorizedTransactionId);
// //
await this.account() await this.account()

View File

@@ -25,7 +25,7 @@ export class BillAllocateLandedCostController {
private billAllocatedCostTransactions: BillAllocatedLandedCostTransactions, private billAllocatedCostTransactions: BillAllocatedLandedCostTransactions,
private revertAllocatedLandedCost: RevertAllocatedLandedCost, private revertAllocatedLandedCost: RevertAllocatedLandedCost,
private landedCostTransactions: LandedCostTranasctions, private landedCostTransactions: LandedCostTranasctions,
) {} ) { }
@Get('/transactions') @Get('/transactions')
@ApiOperation({ summary: 'Get landed cost transactions' }) @ApiOperation({ summary: 'Get landed cost transactions' })

View File

@@ -21,7 +21,7 @@ export class BillAllocatedLandedCostTransactions {
private readonly billLandedCostModel: TenantModelProxy< private readonly billLandedCostModel: TenantModelProxy<
typeof BillLandedCost typeof BillLandedCost
>, >,
) {} ) { }
/** /**
* Retrieve the bill associated landed cost transactions. * Retrieve the bill associated landed cost transactions.
@@ -77,6 +77,13 @@ export class BillAllocatedLandedCostTransactions {
transaction.fromTransactionType, transaction.fromTransactionType,
transaction, transaction,
); );
const allocationMethodFormattedKey = transaction.allocationMethodFormatted;
const allocationMethodFormatted = allocationMethodFormattedKey
? this.i18nService.t(allocationMethodFormattedKey, {
defaultValue: allocationMethodFormattedKey,
})
: '';
return { return {
formattedAmount: formatNumber(transaction.amount, { formattedAmount: formatNumber(transaction.amount, {
currencyCode: transaction.currencyCode, currencyCode: transaction.currencyCode,
@@ -84,12 +91,14 @@ export class BillAllocatedLandedCostTransactions {
...omit(transaction, [ ...omit(transaction, [
'allocatedFromBillEntry', 'allocatedFromBillEntry',
'allocatedFromExpenseEntry', 'allocatedFromExpenseEntry',
'allocationMethodFormatted',
]), ]),
name, name,
description, description,
formattedLocalAmount: formatNumber(transaction.localAmount, { formattedLocalAmount: formatNumber(transaction.localAmount, {
currencyCode: 'USD', currencyCode: 'USD',
}), }),
allocationMethodFormatted,
}; };
}; };

View File

@@ -14,7 +14,7 @@ import { LandedCostTransactionsQueryDto } from '../dtos/LandedCostTransactionsQu
@Injectable() @Injectable()
export class LandedCostTranasctions { export class LandedCostTranasctions {
constructor(private readonly transactionLandedCost: TransactionLandedCost) {} constructor(private readonly transactionLandedCost: TransactionLandedCost) { }
/** /**
* Retrieve the landed costs based on the given query. * Retrieve the landed costs based on the given query.
@@ -45,8 +45,8 @@ export class LandedCostTranasctions {
)(transactionType); )(transactionType);
return pipe( return pipe(
this.transformLandedCostTransactions,
R.map(transformLandedCost), R.map(transformLandedCost),
this.transformLandedCostTransactions,
)(transactions); )(transactions);
}; };
@@ -90,7 +90,7 @@ export class LandedCostTranasctions {
const entries = R.map< const entries = R.map<
ILandedCostTransactionEntry, ILandedCostTransactionEntry,
ILandedCostTransactionEntryDOJO ILandedCostTransactionEntryDOJO
>(transformLandedCostEntry)(transaction.entries); >(transformLandedCostEntry)(transaction.entries ?? []);
return { return {
...transaction, ...transaction,

View File

@@ -4,7 +4,6 @@ import {
IsOptional, IsOptional,
IsArray, IsArray,
ValidateNested, ValidateNested,
IsDecimal,
IsString, IsString,
IsNumber, IsNumber,
} from 'class-validator'; } from 'class-validator';
@@ -17,8 +16,9 @@ export class AllocateBillLandedCostItemDto {
@ToNumber() @ToNumber()
entryId: number; entryId: number;
@IsDecimal() @IsNumber()
cost: string; // Use string for IsDecimal, or use @IsNumber() if you want a number @ToNumber()
cost: number;
} }
export class AllocateBillLandedCostDto { export class AllocateBillLandedCostDto {

View File

@@ -60,8 +60,8 @@ export class BillLandedCost extends BaseModel {
const allocationMethod = lowerCase(this.allocationMethod); const allocationMethod = lowerCase(this.allocationMethod);
const keyLabelsPairs = { const keyLabelsPairs = {
value: 'allocation_method.value.label', value: 'bill.allocation_method.value',
quantity: 'allocation_method.quantity.label', quantity: 'bill.allocation_method.quantity',
}; };
return keyLabelsPairs[allocationMethod] || ''; return keyLabelsPairs[allocationMethod] || '';
} }

View File

@@ -1,14 +1,115 @@
// @ts-nocheck // @ts-nocheck
import React, { useReducer, useEffect } from 'react'; import React, { useReducer, useEffect } from 'react';
import classNames from 'classnames';
import { Button, ButtonGroup, Intent, HTMLSelect } from '@blueprintjs/core'; import { Button, ButtonGroup, Intent, HTMLSelect } from '@blueprintjs/core';
import { FormattedMessage as T } from '@/components';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { range } from 'lodash'; 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 = { const TYPE = {
PAGE_CHANGE: 'PAGE_CHANGE', PAGE_CHANGE: 'PAGE_CHANGE',
@@ -91,6 +192,7 @@ export function Pagination({
onPageChange, onPageChange,
onPageSizeChange, onPageSizeChange,
}) { }) {
const isDark = useIsDarkMode();
const [state, dispatch] = useReducer( const [state, dispatch] = useReducer(
reducer, reducer,
{ currentPage, total, size }, { currentPage, total, size },
@@ -107,10 +209,10 @@ export function Pagination({
}, [total, size, currentPage]); }, [total, size, currentPage]);
return ( return (
<div class="pagination"> <x.div display="flex" p="20px 14px" fontSize="13px">
<div class="pagination__buttons-group"> <x.div>
<ButtonGroup> <StyledButtonGroup>
<Button <StyledPreviousButton
disabled={state.currentPage <= 1} disabled={state.currentPage <= 1}
onClick={() => { onClick={() => {
dispatch({ type: 'PAGE_CHANGE', page: state.currentPage - 1 }); dispatch({ type: 'PAGE_CHANGE', page: state.currentPage - 1 });
@@ -121,14 +223,13 @@ export function Pagination({
onPageChange({ page, pageSize }); onPageChange({ page, pageSize });
}} }}
minimal={true} minimal={true}
className={'pagination__item pagination__item--previous'}
icon={<Icon icon={'arrow-back-24'} iconSize={12} />} icon={<Icon icon={'arrow-back-24'} iconSize={12} />}
> >
<T id="previous" /> <T id="previous" />
</Button> </StyledPreviousButton>
{state.pages.map((page) => ( {state.pages.map((page) => (
<Button <StyledPaginationButton
key={page} key={page}
intent={state.currentPage === page ? Intent.PRIMARY : Intent.NONE} intent={state.currentPage === page ? Intent.PRIMARY : Intent.NONE}
disabled={state.currentPage === page} disabled={state.currentPage === page}
@@ -139,18 +240,12 @@ export function Pagination({
onPageChange({ page, pageSize }); onPageChange({ page, pageSize });
}} }}
minimal={true} minimal={true}
className={classNames( className={state.currentPage === page ? 'is-active' : ''}
'pagination__item',
'pagination__item--page',
{
'is-active': state.currentPage === page,
},
)}
> >
{page} {page}
</Button> </StyledPaginationButton>
))} ))}
<Button <StyledNextButton
disabled={state.currentPage === state.totalPages} disabled={state.currentPage === state.totalPages}
onClick={() => { onClick={() => {
dispatch({ dispatch({
@@ -163,18 +258,17 @@ export function Pagination({
onPageChange({ page, pageSize }); onPageChange({ page, pageSize });
}} }}
minimal={true} minimal={true}
className={'pagination__item pagination__item--next'}
icon={<Icon icon={'arrow-forward-24'} iconSize={12} />} icon={<Icon icon={'arrow-forward-24'} iconSize={12} />}
> >
<T id="next" /> <T id="next" />
</Button> </StyledNextButton>
</ButtonGroup> </StyledButtonGroup>
</div> </x.div>
<div class="pagination__controls"> <x.div display="flex" alignItems="center" ml="auto">
<div class="pagination__goto-control"> <x.div display="none">
Go to Go to
<HTMLSelect <StyledHTMLSelect
minimal={true} minimal={true}
options={range(1, state.totalPages + 1)} options={range(1, state.totalPages + 1)}
value={state.currentPage} value={state.currentPage}
@@ -186,11 +280,11 @@ export function Pagination({
onPageChange({ page, pageSize }); onPageChange({ page, pageSize });
}} }}
/> />
</div> </x.div>
<div class="pagination__pagesize-control"> <x.div ml="12px" color={isDark ? 'rgba(255, 255, 255, 0.6)' : '#666'}>
<T id={'page_size'} /> <T id={'page_size'} />
<HTMLSelect <StyledHTMLSelect
minimal={true} minimal={true}
options={pageSizesOptions} options={pageSizesOptions}
value={size} value={size}
@@ -202,17 +296,17 @@ export function Pagination({
onPageSizeChange({ pageSize, page: 1 }); onPageSizeChange({ pageSize, page: 1 });
}} }}
/> />
</div> </x.div>
</div> </x.div>
<div class="pagination__info"> <x.div color={isDark ? 'rgba(255, 255, 255, 0.6)' : '#666'} ml="12px" display="flex" alignItems="center">
{intl.get('showing_current_page_to_total', { {intl.get('showing_current_page_to_total', {
currentPage: state.currentPage, currentPage: state.currentPage,
totalPages: state.totalPages, totalPages: state.totalPages,
total: total, total: total,
})} })}
</div> </x.div>
</div> </x.div>
); );
} }

View File

@@ -2,6 +2,7 @@
import React from 'react'; import React from 'react';
import { getColumnWidth } from '@/utils'; import { getColumnWidth } from '@/utils';
import { useExcludedTransactionsBoot } from './ExcludedTransactionsTableBoot'; import { useExcludedTransactionsBoot } from './ExcludedTransactionsTableBoot';
import { CLASSES } from '@/constants';
const getReportColWidth = (data, accessor, headerText) => { const getReportColWidth = (data, accessor, headerText) => {
return getColumnWidth( return getColumnWidth(
@@ -13,7 +14,7 @@ const getReportColWidth = (data, accessor, headerText) => {
}; };
const descriptionAccessor = (transaction) => { const descriptionAccessor = (transaction) => {
return <span style={{ color: '#5F6B7C' }}>{transaction.description}</span>; return <span className={CLASSES.TEXT_MUTED}>{transaction.description}</span>;
}; };
/** /**

View File

@@ -24,7 +24,7 @@ function AllocateLandedCostDialogProvider({
dialogName, dialogName,
...props ...props
}) { }) {
const [transactionsType, setTransactionsType] = React.useState(null); const [transactionsType, setTransactionsType] = React.useState('Bill');
const [transactionId, setTransactionId] = React.useState(null); const [transactionId, setTransactionId] = React.useState(null);
const [transactionEntryId, setTransactionEntryId] = React.useState(null); const [transactionEntryId, setTransactionEntryId] = React.useState(null);
@@ -34,7 +34,8 @@ function AllocateLandedCostDialogProvider({
}); });
// Retrieve the landed cost transactions based on the given transactions type. // Retrieve the landed cost transactions based on the given transactions type.
const { const {
data: { transactions: landedCostTransactions }, data: landedCostTransactions,
isLoading: isLandedCostTransactionsLoading,
} = useLandedCostTransaction(transactionsType, { } = useLandedCostTransaction(transactionsType, {
enabled: !!transactionsType, enabled: !!transactionsType,
}); });
@@ -88,6 +89,7 @@ function AllocateLandedCostDialogProvider({
costTransactionEntries, costTransactionEntries,
transactionsType, transactionsType,
landedCostTransactions, landedCostTransactions,
isLandedCostTransactionsLoading,
setTransactionsType, setTransactionsType,
setTransactionId, setTransactionId,
setTransactionEntryId, setTransactionEntryId,

View File

@@ -38,7 +38,7 @@ function AllocateLandedCostFloatingActions({
<DialogFooterActions alignment={'left'}> <DialogFooterActions alignment={'left'}>
{costTransactionEntry && ( {costTransactionEntry && (
<UnallocatedAmount> <UnallocatedAmount>
<T id={'landed_cost.dialog.label_unallocated_cost_amount'}/> <T id={'landed_cost.dialog.label_unallocated_cost_amount'} />
<strong>{formattedUnallocatedCostAmount}</strong> <strong>{formattedUnallocatedCostAmount}</strong>
</UnallocatedAmount> </UnallocatedAmount>
)} )}
@@ -68,11 +68,16 @@ const AllocateDialogFooter = styled(DialogFooter)`
`; `;
const UnallocatedAmount = styled.div` const UnallocatedAmount = styled.div`
color: #3f5278; --x-color-text: #3f5278;
.bp4-dark & {
--x-color-text: var(--color-light-gray1);
}
color: var(--x-color-text);
align-self: center; align-self: center;
strong { strong {
color: #353535; color: var(--x-color-text);
padding-left: 4px; padding-left: 4px;
} }
`; `;

View File

@@ -8,8 +8,10 @@ import {
RadioGroup, RadioGroup,
Radio, Radio,
InputGroup, InputGroup,
Spinner,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { x } from '@xstyled/emotion';
import { FormattedMessage as T, If, FFormGroup, FSelect, FRadioGroup, FInputGroup } from '@/components'; import { FormattedMessage as T, If, FFormGroup, FSelect, FRadioGroup, FInputGroup } from '@/components';
import { handleStringChange } from '@/utils'; import { handleStringChange } from '@/utils';
import { FieldRequiredHint } from '@/components'; import { FieldRequiredHint } from '@/components';
@@ -28,7 +30,7 @@ import { useAllocateLandedConstDialogContext } from './AllocateLandedCostDialogP
*/ */
export default function AllocateLandedCostFormFields() { export default function AllocateLandedCostFormFields() {
// Allocated landed cost dialog. // Allocated landed cost dialog.
const { costTransactionEntries, landedCostTransactions } = const { costTransactionEntries, landedCostTransactions, isLandedCostTransactionsLoading } =
useAllocateLandedConstDialogContext(); useAllocateLandedConstDialogContext();
const { values, setFieldValue, form } = useFormikContext(); const { values, setFieldValue, form } = useFormikContext();
@@ -97,9 +99,10 @@ export default function AllocateLandedCostFormFields() {
inline inline
fill fill
> >
<x.div position="relative" w="100%">
<FSelect <FSelect
name={'transaction_id'} name={'transaction_id'}
items={landedCostTransactions} items={landedCostTransactions || []}
onItemChange={handleTransactionChange} onItemChange={handleTransactionChange}
filterable={false} filterable={false}
valueAccessor={'id'} valueAccessor={'id'}
@@ -107,11 +110,24 @@ export default function AllocateLandedCostFormFields() {
labelAccessor={'formatted_unallocated_cost_amount'} labelAccessor={'formatted_unallocated_cost_amount'}
placeholder={intl.get('landed_cost.dialog.label_select_transaction')} placeholder={intl.get('landed_cost.dialog.label_select_transaction')}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
disabled={isLandedCostTransactionsLoading}
/> />
{isLandedCostTransactionsLoading && (
<x.div
position="absolute"
right="35px"
top="50%"
transform="translateY(-50%)"
pointerEvents="none"
>
<Spinner size={16} />
</x.div>
)}
</x.div>
</FFormGroup> </FFormGroup>
{/*------------ Transaction line -----------*/} {/*------------ Transaction line -----------*/}
<If condition={costTransactionEntries.length > 0}> <If condition={costTransactionEntries?.length > 0}>
<FFormGroup <FFormGroup
name={'transaction_entry_id'} name={'transaction_entry_id'}
label={<T id={'transaction_line'} />} label={<T id={'transaction_line'} />}

View File

@@ -32,13 +32,12 @@ function BadDebtForm({
// #withCurrentOrganization // #withCurrentOrganization
organization: { base_currency }, organization: { base_currency },
}) { }) {
const { invoice, dialogName, createBadDebtMutate, cancelBadDebtMutate } = const { invoice, dialogName, createBadDebtMutate } = useBadDebtContext();
useBadDebtContext();
// Initial form values // Initial form values
const initialValues = { const initialValues = {
...defaultInitialValues, ...defaultInitialValues,
amount: invoice.due_amount, amount: invoice?.due_amount || '',
}; };
// Handles the form submit. // Handles the form submit.
@@ -67,7 +66,7 @@ function BadDebtForm({
} }
setSubmitting(false); setSubmitting(false);
}; };
createBadDebtMutate([invoice.id, form]).then(onSuccess).catch(onError); createBadDebtMutate([invoice?.id, form]).then(onSuccess).catch(onError);
}; };
return ( return (

View File

@@ -5,6 +5,7 @@ import {
FMoneyInputGroup, FMoneyInputGroup,
FTextArea, FTextArea,
FormattedMessage as T, FormattedMessage as T,
FFormGroup,
} from '@/components'; } from '@/components';
import { useAutofocus } from '@/hooks'; import { useAutofocus } from '@/hooks';
@@ -53,7 +54,7 @@ function BadDebtFormFields() {
fill fill
> >
<ControlGroup> <ControlGroup>
<InputPrependText text={invoice.currency_code} /> <InputPrependText text={invoice?.currency_code || ''} />
<FMoneyInputGroup <FMoneyInputGroup
name={'amount'} name={'amount'}
minimal={true} minimal={true}
@@ -73,6 +74,7 @@ function BadDebtFormFields() {
name={'expense_account_id'} name={'expense_account_id'}
items={accounts} items={accounts}
filterByTypes={[ACCOUNT_TYPE.EXPENSE]} filterByTypes={[ACCOUNT_TYPE.EXPENSE]}
fill
/> />
</FFormGroup> </FFormGroup>

View File

@@ -16,7 +16,7 @@ function BadDebtDialog({ dialogName, payload: { invoiceId = null }, isOpen }) {
name={dialogName} name={dialogName}
title={<T id={'bad_debt.dialog.bad_debt'} />} title={<T id={'bad_debt.dialog.bad_debt'} />}
isOpen={isOpen} isOpen={isOpen}
canEscapeJeyClose={true} canEscapeKeyClose={true}
autoFocus={true} autoFocus={true}
className={'dialog--bad-debt'} className={'dialog--bad-debt'}
> >

View File

@@ -424,7 +424,7 @@ export function useUnexcludeUncategorizedTransactions(
UnexcludeBankTransactionsValue UnexcludeBankTransactionsValue
>( >(
(value: { ids: Array<number | string> }) => (value: { ids: Array<number | string> }) =>
apiRequest.delete(`/banking/exclude/bulk`, { ids: value.ids }), apiRequest.delete(`/banking/exclude/bulk`, { data: { ids: value.ids } }),
{ {
onSuccess: (res, id) => { onSuccess: (res, id) => {
onValidateExcludeUncategorizedTransaction(queryClient); onValidateExcludeUncategorizedTransaction(queryClient);

View File

@@ -341,7 +341,7 @@ export function useCancelBadDebt(props) {
const apiRequest = useApiRequest(); const apiRequest = useApiRequest();
return useMutation( return useMutation(
(id) => apiRequest.post(`sale-invoices/${id}/writeoff/cancel`), (id) => apiRequest.post(`sale-invoices/${id}/cancel-writeoff`),
{ {
onSuccess: (res, id) => { onSuccess: (res, id) => {
// Invalidate // Invalidate

View File

@@ -67,9 +67,6 @@ export function useLandedCostTransaction(query, props) {
}, },
{ {
select: (res) => res.data, select: (res) => res.data,
defaultData: {
transactions: [],
},
...props, ...props,
}, },
); );

View File

@@ -21,7 +21,7 @@
} }
.bp4-dialog-footer{ .bp4-dialog-footer{
padding-top: 10px; // padding-top: 10px;
} }
.bigcapital-datatable { .bigcapital-datatable {
@@ -30,6 +30,10 @@
border: 1px solid #d1dee2; border: 1px solid #d1dee2;
min-width: auto; min-width: auto;
.bp4-dark & {
border-color: var(--color-dark-gray5);
}
.tbody, .tbody,
.tbody-inner { .tbody-inner {
height: auto; height: auto;
@@ -43,6 +47,10 @@
padding: 0.4rem; padding: 0.4rem;
margin-left: -1px; margin-left: -1px;
border-left: 1px solid #ececec; border-left: 1px solid #ececec;
.bp4-dark & {
border-left-color: var(--color-dark-gray5);
}
} }
.bp4-form-group{ .bp4-form-group{
@@ -51,6 +59,10 @@
&:not(.bp4-intent-danger) .bp4-input{ &:not(.bp4-intent-danger) .bp4-input{
border: 1px solid #d0dfe2; border: 1px solid #d0dfe2;
.bp4-dark & {
border-color: var(--color-dark-gray5);
}
&:focus{ &:focus{
box-shadow: 0 0 0 1px #116cd0; box-shadow: 0 0 0 1px #116cd0;
border-color: #116cd0; border-color: #116cd0;