Compare commits

...

18 Commits

Author SHA1 Message Date
QT
18ab1f6ae3 Merge branch 'main' into develop
Some checks are pending
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Waiting to run
Build and Deploy Develop Docker Container / Build and deploy server container (push) Waiting to run
2026-02-05 23:18:49 +00:00
QT
5ea3ac14e4 Update docker-compose.prod.yml
Some checks failed
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
2026-02-05 23:16:27 +00:00
QT
84079df772 Merge pull request 'develop' (#1) from develop into main
Some checks are pending
E2E / Test setup (push) Waiting to run
E2E / Playwright tests (push) Blocked by required conditions
TypeCheck / TypeScript Type Check (push) Waiting to run
Reviewed-on: #1
2026-02-05 23:10:08 +00:00
QT
28a8b0b5d1 Update docker-compose.prod.yml
Some checks failed
E2E / Test setup (pull_request) Waiting to run
E2E / Playwright tests (pull_request) Blocked by required conditions
TypeCheck / TypeScript Type Check (pull_request) Waiting to run
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
2026-02-05 23:01:31 +00:00
Ahmed Bouhuolia
2c05785096 Merge pull request #934 from bigcapitalhq/fix/branches-activation-bills
fix(server): branches activation not marking bills and payments with primary branch
2026-02-05 16:06:39 +02:00
Ahmed Bouhuolia
6af4be9c6c fix(server): branches activation not marking bills and payments with primary branch
When activating the multi-branches feature, existing bills, vendor credits,
and bill payments were not being marked with the default primary branch.

Changes:
- Add missing @Inject decorators to BillActivateBranches, VendorCreditActivateBranches,
  and BillPaymentsActivateBranches services
- Create BillBranchesActivateSubscriber to listen to onActivated event
- Create VendorCreditBranchesActivateSubscriber to listen to onActivated event
- Register BillPaymentsActivateBranches and PaymentMadeActivateBranchesSubscriber
  in BranchesModule
- Add branch object to BillResponseDto for API responses
- Add branch to BillTransformer includeAttributes

Fixes: #935
2026-02-05 16:03:57 +02:00
Ahmed Bouhuolia
8def1d31d2 Merge pull request #933 from bigcapitalhq/20260205-151219-7770
feat(webapp): add blurry background to sticky data table cells
2026-02-05 15:29:47 +02:00
Ahmed Bouhuolia
afab02a053 feat(webapp): add blurry background to sticky data table cells
Add backdrop-filter blur effect to sticky column cells in financial reports
to prevent content from showing through during horizontal scrolling.
The effect only applies when rows are not hovered to preserve hover
background interactions.
2026-02-05 15:27:45 +02:00
Ahmed Bouhuolia
8e925c62f2 Merge pull request #932 from bigcapitalhq/20260205-151219-7770
fix(server): balance sheet query validation schema
2026-02-05 15:14:45 +02:00
Ahmed Bouhuolia
1b7d513adf fix(server): balance sheet query validation schema 2026-02-05 15:12:54 +02:00
Ahmed Bouhuolia
7d764fb390 Merge pull request #931 from bigcapitalhq/fix/item-error-handling
fix(items): correct error type handling and add swagger documentation
2026-02-04 21:44:45 +02:00
Ahmed Bouhuolia
c571f50a74 fix(items): correct error type handling and add swagger documentation
- Fix error type mismatch: change 'ITEM.NAME.ALREADY.EXISTS' to 'ITEM_NAME_EXISTS'
- Add ItemErrorType constant with UpperCamelCase keys for better maintainability
- Update all error checks to use the new ItemErrorType constant
- Add ItemErrorResponse.dto.ts with documented error types for swagger
- Add @ApiResponse decorators to document 400 validation errors in swagger
2026-02-04 21:42:39 +02:00
Ahmed Bouhuolia
6549026344 Merge pull request #930 from bigcapitalhq/fix/account-delete-error-handling
fix(webapp): account delete error handling response types
2026-02-04 21:31:38 +02:00
Ahmed Bouhuolia
0963394b04 fix(webapp): account delete error handling response types 2026-02-04 21:27:25 +02:00
Ahmed Bouhuolia
6cab0651fc Merge pull request #927 from bigcapitalhq/feature/20260202223150
fix(webapp): darkmode warehouses list page
2026-02-02 22:36:42 +02:00
Ahmed Bouhuolia
4af537d6dd fix(webapp): darkmode warehouses list page 2026-02-02 22:31:53 +02:00
Ahmed Bouhuolia
34db64612c Merge pull request #926 from bigcapitalhq/20260202-185120-9c84
fix(webapp): constrant not found row color
2026-02-02 18:53:48 +02:00
Ahmed Bouhuolia
10225bbfed fix(webapp): constrant not found row color 2026-02-02 18:51:52 +02:00
32 changed files with 417 additions and 77 deletions

View File

@@ -9,8 +9,8 @@ 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

View File

@@ -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,

View File

@@ -30,6 +30,7 @@ export class BillTransformer extends Transformer {
'taxes',
'entries',
'attachments',
'branch',
];
};

View File

@@ -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,

View File

@@ -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 });
};
}

View File

@@ -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>,
) {}

View File

@@ -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>,
) {}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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',

View File

@@ -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',

View File

@@ -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()

View File

@@ -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',

View 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[];
}

View File

@@ -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);
}
`;

View File

@@ -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} />
) : (

View File

@@ -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' },
];

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
export default {
export const App = {
"app_name": "BigCapital",
"app_version": "0.0.1 (build 12344)",
}

View File

@@ -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' },
];

View File

@@ -16,7 +16,7 @@ import {
VendorAction,
} from './abilityOption';
export default [
export const KeyboardShortcutsOptions = [
{
shortcut_key: 'Shift + I',
description: intl.get('jump_to_the_invoices'),

View File

@@ -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,

View File

@@ -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,

View File

@@ -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'}

View File

@@ -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'}

View File

@@ -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),

View File

@@ -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) => {

View File

@@ -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({

View File

@@ -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;

View File

@@ -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],
);
};

View File

@@ -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')),

View File

@@ -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.

View File

@@ -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);
}
}
}