Merge pull request #881 from bigcapitalhq/bugs-bashing

bugs bashing
This commit is contained in:
Ahmed Bouhuolia
2025-12-29 22:08:56 +02:00
committed by GitHub
39 changed files with 372 additions and 528 deletions

View File

@@ -211,7 +211,7 @@ export class InventoryValuationSheet extends FinancialSheet {
* Detarmines whether the items post filter is active. * Detarmines whether the items post filter is active.
*/ */
private isItemsPostFilter = (): boolean => { private isItemsPostFilter = (): boolean => {
return isEmpty(this.query.itemsIds); return !isEmpty(this.query.itemsIds);
}; };
/** /**

View File

@@ -18,7 +18,7 @@ export class InventoryValuationSheetService {
private readonly inventoryValuationMeta: InventoryValuationMetaInjectable, private readonly inventoryValuationMeta: InventoryValuationMetaInjectable,
private readonly eventPublisher: EventEmitter2, private readonly eventPublisher: EventEmitter2,
private readonly inventoryValuationSheetRepository: InventoryValuationSheetRepository, private readonly inventoryValuationSheetRepository: InventoryValuationSheetRepository,
) {} ) { }
/** /**
* Inventory valuation sheet. * Inventory valuation sheet.

View File

@@ -172,7 +172,10 @@ export class TrialBalanceSheet extends FinancialSheet {
private filterNoneTransactions = ( private filterNoneTransactions = (
accountNode: ITrialBalanceAccount accountNode: ITrialBalanceAccount
): boolean => { ): boolean => {
return false === this.repository.totalAccountsLedger.isEmpty(); const accountLedger = this.repository.totalAccountsLedger.whereAccountId(
accountNode.id,
);
return !accountLedger.isEmpty();
}; };
/** /**

View File

@@ -93,7 +93,7 @@ export class InventoryComputeCostService {
*/ */
async scheduleComputeItemCost(itemId: number, startingDate: Date | string) { async scheduleComputeItemCost(itemId: number, startingDate: Date | string) {
const debounceKey = `inventory-cost-compute-debounce:${itemId}`; const debounceKey = `inventory-cost-compute-debounce:${itemId}`;
const debounceTime = 1000 * 60; // 1 minute const debounceTime = 1000 * 10; // 10 seconds
// Generate a unique job ID or use a custom identifier // Generate a unique job ID or use a custom identifier
const jobId = `task-${Date.now()}-${Math.random().toString(36).substring(2)}`; const jobId = `task-${Date.now()}-${Math.random().toString(36).substring(2)}`;

View File

@@ -2,7 +2,8 @@ import { EventEmitter2 } from '@nestjs/event-emitter';
import { Processor, WorkerHost } from '@nestjs/bullmq'; import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Scope } from '@nestjs/common'; import { Scope } from '@nestjs/common';
import { Job } from 'bullmq'; import { Job } from 'bullmq';
import { ClsService } from 'nestjs-cls'; import { ClsService, UseCls } from 'nestjs-cls';
import * as moment from 'moment';
import { TenantJobPayload } from '@/interfaces/Tenant'; import { TenantJobPayload } from '@/interfaces/Tenant';
import { InventoryComputeCostService } from '../commands/InventoryComputeCost.service'; import { InventoryComputeCostService } from '../commands/InventoryComputeCost.service';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
@@ -14,7 +15,7 @@ import { Process } from '@nestjs/bull';
interface ComputeItemCostJobPayload extends TenantJobPayload { interface ComputeItemCostJobPayload extends TenantJobPayload {
itemId: number; itemId: number;
startingDate: Date; startingDate: Date | string;
} }
@Processor({ @Processor({
name: ComputeItemCostQueue, name: ComputeItemCostQueue,
@@ -39,28 +40,34 @@ export class ComputeItemCostProcessor extends WorkerHost {
* @param {Job<ComputeItemCostJobPayload>} job - The job to process * @param {Job<ComputeItemCostJobPayload>} job - The job to process
*/ */
@Process(ComputeItemCostQueueJob) @Process(ComputeItemCostQueueJob)
@UseCls()
async process(job: Job<ComputeItemCostJobPayload>) { async process(job: Job<ComputeItemCostJobPayload>) {
const { itemId, startingDate, organizationId, userId } = job.data; const { itemId, startingDate, organizationId, userId } = job.data;
console.log(`Compute item cost for item ${itemId} started`); // Parse startingDate using moment to handle both Date and string formats
const startingDateObj = moment(startingDate).toDate();
console.log(`[info] Compute item cost for item ${itemId} started`, {
payload: job.data,
jobId: job.id
});
this.clsService.set('organizationId', organizationId); this.clsService.set('organizationId', organizationId);
this.clsService.set('userId', userId); this.clsService.set('userId', userId);
try { try {
await this.inventoryComputeCostService.computeItemCost( await this.inventoryComputeCostService.computeItemCost(
startingDate, startingDateObj,
itemId, itemId,
); );
// Emit job completed event // Emit job completed event
await this.eventEmitter.emitAsync( await this.eventEmitter.emitAsync(
events.inventory.onComputeItemCostJobCompleted, events.inventory.onComputeItemCostJobCompleted,
{ startingDate, itemId, organizationId, userId }, { startingDate: startingDateObj, itemId, organizationId, userId },
); );
console.log(`[info] Compute item cost for item ${itemId} completed successfully`);
console.log(`Compute item cost for item ${itemId} completed`);
} catch (error) { } catch (error) {
console.error('Error computing item cost:', error); console.error(`[error] Error computing item cost for item ${itemId}:`, error);
console.error('Error stack:', error instanceof Error ? error.stack : 'No stack trace');
throw error; throw error;
} }
} }

View File

@@ -19,7 +19,7 @@ export class SaleInvoiceCostGLEntries {
private readonly inventoryCostLotTracker: TenantModelProxy< private readonly inventoryCostLotTracker: TenantModelProxy<
typeof InventoryCostLotTracker typeof InventoryCostLotTracker
>, >,
) {} ) { }
/** /**
* Writes journal entries from sales invoices. * Writes journal entries from sales invoices.

View File

@@ -10,7 +10,7 @@ import { events } from '@/common/events/events';
@Injectable() @Injectable()
export class InvoiceGLEntriesSubscriber { export class InvoiceGLEntriesSubscriber {
constructor(public readonly saleInvoiceGLEntries: SaleInvoiceGLEntries) {} constructor(public readonly saleInvoiceGLEntries: SaleInvoiceGLEntries) { }
/** /**
* Records journal entries of the non-inventory invoice. * Records journal entries of the non-inventory invoice.

View File

@@ -26,7 +26,7 @@ export class TransactionsLockingService {
constructor( constructor(
private readonly transactionsLockingRepo: TransactionsLockingRepository, private readonly transactionsLockingRepo: TransactionsLockingRepository,
private readonly eventPublisher: EventEmitter2, private readonly eventPublisher: EventEmitter2,
) {} ) { }
/** /**
* Enable/disable all transacations locking. * Enable/disable all transacations locking.

View File

@@ -20,12 +20,3 @@ export function BranchSelect({ branches, ...rest }) {
/> />
); );
} }
/**
*
* @param {*} param0
* @returns
*/
export function BranchSelectButton({ label, ...rest }) {
return <Button text={label} {...rest} />;
}

View File

@@ -171,6 +171,15 @@ export const financialReportMenus = [
subject: AbilitySubject.Report, subject: AbilitySubject.Report,
ability: ReportsAction.READ_INVENTORY_ITEM_DETAILS, ability: ReportsAction.READ_INVENTORY_ITEM_DETAILS,
}, },
{
title: <T id={'inventory_valuation'} />,
desc: (
<T id={'summerize_your_transactions_for_each_inventory_item'} />
),
link: '/financial-reports/inventory-valuation',
subject: AbilitySubject.Report,
ability: ReportsAction.READ_INVENTORY_VALUATION_SUMMARY,
},
], ],
}, },
{ {

View File

@@ -1,7 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { FastField, Field, ErrorMessage } from 'formik'; import { ErrorMessage, useFormikContext } from 'formik';
import { import {
Classes, Classes,
FormGroup, FormGroup,
@@ -10,15 +10,14 @@ import {
InputGroup, InputGroup,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormattedMessage as T, If } from '@/components'; import { FormattedMessage as T, If, FFormGroup, FSelect, FRadioGroup, FInputGroup } from '@/components';
import { inputIntent, handleStringChange } from '@/utils'; import { handleStringChange } from '@/utils';
import { FieldRequiredHint, ListSelect } from '@/components'; import { FieldRequiredHint } from '@/components';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import allocateLandedCostType from '@/constants/allocateLandedCostType'; import allocateLandedCostType from '@/constants/allocateLandedCostType';
import AllocateLandedCostFormBody from './AllocateLandedCostFormBody'; import AllocateLandedCostFormBody from './AllocateLandedCostFormBody';
import { import {
transactionsSelectShouldUpdate,
allocateCostToEntries, allocateCostToEntries,
resetAllocatedCostEntries, resetAllocatedCostEntries,
} from './utils'; } from './utils';
@@ -32,196 +31,157 @@ export default function AllocateLandedCostFormFields() {
const { costTransactionEntries, landedCostTransactions } = const { costTransactionEntries, landedCostTransactions } =
useAllocateLandedConstDialogContext(); useAllocateLandedConstDialogContext();
return ( const { values, setFieldValue, form } = useFormikContext();
<div className={Classes.DIALOG_BODY}>
{/*------------Transaction type -----------*/} // Handle transaction type select change.
<FastField const handleTransactionTypeChange = (type) => {
name={'transaction_type'}
transactions={allocateLandedCostType}
shouldUpdate={transactionsSelectShouldUpdate}
>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'transaction_type'} />}
labelInfo={<FieldRequiredHint />}
helperText={<ErrorMessage name="transaction_type" />}
intent={inputIntent({ error, touched })}
inline={true}
className={classNames(CLASSES.FILL, 'form-group--transaction_type')}
>
<ListSelect
items={allocateLandedCostType}
onItemSelect={(type) => {
const { items } = values; const { items } = values;
setFieldValue('transaction_type', type.value); setFieldValue('transaction_type', type.value);
setFieldValue('transaction_id', ''); setFieldValue('transaction_id', '');
setFieldValue('transaction_entry_id', ''); setFieldValue('transaction_entry_id', '');
setFieldValue('amount', ''); setFieldValue('amount', '');
setFieldValue('items', resetAllocatedCostEntries(items)); setFieldValue('items', resetAllocatedCostEntries(items));
}} };
// Handle transaction select change.
const handleTransactionChange = (transaction) => {
const { items } = values;
setFieldValue('transaction_id', transaction.id);
setFieldValue('transaction_entry_id', '');
setFieldValue('amount', '');
setFieldValue('items', resetAllocatedCostEntries(items));
};
// Handle transaction entry select change.
const handleTransactionEntryChange = (entry) => {
const { id, unallocated_cost_amount: unallocatedAmount } = entry;
const { items, allocation_method } = values;
setFieldValue('amount', unallocatedAmount);
setFieldValue('transaction_entry_id', id);
setFieldValue(
'items',
allocateCostToEntries(unallocatedAmount, allocation_method, items),
);
};
return (
<div className={Classes.DIALOG_BODY}>
{/*------------Transaction type -----------*/}
<FFormGroup
name={'transaction_type'}
label={<T id={'transaction_type'} />}
labelInfo={<FieldRequiredHint />}
inline
fill
fastField
>
<FSelect
name={'transaction_type'}
items={allocateLandedCostType}
onItemChange={handleTransactionTypeChange}
filterable={false} filterable={false}
selectedItem={value} valueAccessor={'value'}
selectedItemProp={'value'} textAccessor={'name'}
textProp={'name'}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
fastField
/> />
</FormGroup> </FFormGroup>
)}
</FastField>
{/*------------ Transaction -----------*/} {/*------------ Transaction -----------*/}
<Field <FFormGroup
name={'transaction_id'} name={'transaction_id'}
transactions={landedCostTransactions}
shouldUpdate={transactionsSelectShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'transaction_id'} />} label={<T id={'transaction_id'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })} inline
helperText={<ErrorMessage name="transaction_id" />} fill
className={classNames(CLASSES.FILL, 'form-group--transaction_id')}
inline={true}
> >
<ListSelect <FSelect
name={'transaction_id'}
items={landedCostTransactions} items={landedCostTransactions}
onItemSelect={({ id }) => { onItemChange={handleTransactionChange}
const { items } = form.values;
form.setFieldValue('transaction_id', id);
form.setFieldValue('transaction_entry_id', '');
form.setFieldValue('amount', '');
form.setFieldValue('items', resetAllocatedCostEntries(items));
}}
filterable={false} filterable={false}
selectedItem={value} valueAccessor={'id'}
selectedItemProp={'id'} textAccessor={'name'}
textProp={'name'} labelAccessor={'formatted_unallocated_cost_amount'}
labelProp={'formatted_unallocated_cost_amount'} placeholder={intl.get('landed_cost.dialog.label_select_transaction')}
defaultText={intl.get(
'landed_cost.dialog.label_select_transaction',
)}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FFormGroup>
)}
</Field>
{/*------------ Transaction line -----------*/} {/*------------ Transaction line -----------*/}
<If condition={costTransactionEntries.length > 0}> <If condition={costTransactionEntries.length > 0}>
<Field <FFormGroup
name={'transaction_entry_id'} name={'transaction_entry_id'}
transactions={costTransactionEntries}
shouldUpdate={transactionsSelectShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'transaction_line'} />} label={<T id={'transaction_line'} />}
intent={inputIntent({ error, touched })} inline
helperText={<ErrorMessage name="transaction_entry_id" />} fill
className={classNames( fastField
CLASSES.FILL,
'form-group--transaction_entry_id',
)}
inline={true}
> >
<ListSelect <FSelect
name={'transaction_entry_id'}
items={costTransactionEntries} items={costTransactionEntries}
onItemSelect={(entry) => { onItemChange={handleTransactionEntryChange}
const { id, unallocated_cost_amount: unallocatedAmount } =
entry;
const { items, allocation_method } = form.values;
form.setFieldValue('amount', unallocatedAmount);
form.setFieldValue('transaction_entry_id', id);
form.setFieldValue(
'items',
allocateCostToEntries(
unallocatedAmount,
allocation_method,
items,
),
);
}}
filterable={false} filterable={false}
selectedItem={value} valueAccessor={'id'}
selectedItemProp={'id'} textAccessor={'name'}
textProp={'name'} labelAccessor={'formatted_unallocated_cost_amount'}
labelProp={'formatted_unallocated_cost_amount'} placeholder={intl.get(
defaultText={intl.get(
'landed_cost.dialog.label_select_transaction_entry', 'landed_cost.dialog.label_select_transaction_entry',
)} )}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
fastField
/> />
</FormGroup> </FFormGroup>
)}
</Field>
</If> </If>
{/*------------ Amount -----------*/} {/*------------ Amount -----------*/}
<FastField name={'amount'}> <FFormGroup
{({ form, field, meta: { error, touched } }) => ( name={'amount'}
<FormGroup
label={<T id={'amount'} />} label={<T id={'amount'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
inline={true} inline={true}
fastField
> >
<InputGroup <FInputGroup
{...field} name={'amount'}
onBlur={(e) => { onBlur={(e) => {
const amount = e.target.value; const amount = e.target.value;
const { allocation_method, items } = form.values; const { allocation_method, items } = values;
form.setFieldValue( setFieldValue(
'items', 'items',
allocateCostToEntries(amount, allocation_method, items), allocateCostToEntries(amount, allocation_method, items),
); );
}} }}
/> />
</FormGroup> </FFormGroup>
)}
</FastField>
{/*------------ Allocation method -----------*/} {/*------------ Allocation method -----------*/}
<Field name={'allocation_method'}> <FFormGroup
{({ form, field: { value }, meta: { touched, error } }) => ( name={'allocation_method'}
<FormGroup
medium={true}
label={<T id={'allocation_method'} />} label={<T id={'allocation_method'} />}
labelInfo={<FieldRequiredHint />} medium
className={'form-group--allocation_method'} inline
intent={inputIntent({ error, touched })} fastField
helperText={<ErrorMessage name="allocation_method" />}
inline={true}
> >
<RadioGroup <FRadioGroup
name={'allocation_method'}
onChange={handleStringChange((_value) => { onChange={handleStringChange((_value) => {
const { amount, items } = form.values; const { amount, items } = values;
form.setFieldValue('allocation_method', _value); setFieldValue('allocation_method', _value);
form.setFieldValue( setFieldValue(
'items', 'items',
allocateCostToEntries(amount, _value, items), allocateCostToEntries(amount, _value, items),
); );
})} })}
selectedValue={value}
inline={true} inline={true}
> >
<Radio label={<T id={'quantity'} />} value="quantity" /> <Radio label={<T id={'quantity'} />} value="quantity" />
<Radio label={<T id={'valuation'} />} value="value" /> <Radio label={<T id={'valuation'} />} value="value" />
</RadioGroup> </FRadioGroup>
</FormGroup> </FFormGroup>
)}
</Field>
{/*------------ Allocate Landed cost Table -----------*/} {/*------------ Allocate Landed cost Table -----------*/}
<AllocateLandedCostFormBody /> <AllocateLandedCostFormBody />

View File

@@ -2,10 +2,9 @@
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { Formik, Form, Field, ErrorMessage } from 'formik'; import { Formik, Form } from 'formik';
import { inputIntent } from '@/utils'; import { FFormGroup, FSelect, FieldRequiredHint } from '@/components';
import { ListSelect, FieldRequiredHint } from '@/components'; import { Button, Intent, Classes } from '@blueprintjs/core';
import { Button, FormGroup, Intent, Classes } from '@blueprintjs/core';
import { FormattedMessage as T } from '@/components'; import { FormattedMessage as T } from '@/components';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useContactDuplicateFromContext } from './ContactDuplicateProvider'; import { useContactDuplicateFromContext } from './ContactDuplicateProvider';
@@ -60,29 +59,22 @@ function ContactDuplicateForm({
</p> </p>
{/*------------ Contact Type -----------*/} {/*------------ Contact Type -----------*/}
<Field name={'contact_type'}> <FFormGroup
{({ form, meta: { error, touched } }) => ( name={'contact_type'}
<FormGroup
label={<T id={'contact_type'} />} label={<T id={'contact_type'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
className={'form-group--select-list'} className={'form-group--select-list'}
helperText={<ErrorMessage name="contact_type" />}
> >
<ListSelect <FSelect
name={'contact_type'}
items={Contacts} items={Contacts}
onItemSelect={({ path }) => placeholder={<T id={'select_contact'} />}
form.setFieldValue('contact_type', path) textAccessor={'name'}
} valueAccessor={'path'}
defaultText={<T id={'select_contact'} />}
textProp={'name'}
selectedItemProp={'name'}
filterable={false} filterable={false}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FFormGroup>
)}
</Field>
</div> </div>
<div className={Classes.DIALOG_FOOTER}> <div className={Classes.DIALOG_FOOTER}>

View File

@@ -3,7 +3,7 @@ import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import styled from 'styled-components'; import styled from 'styled-components';
import classNames from 'classnames'; import classNames from 'classnames';
import { FastField, ErrorMessage, Field } from 'formik'; import { useFormikContext } from 'formik';
import { Classes, FormGroup, Position } from '@blueprintjs/core'; import { Classes, FormGroup, Position } from '@blueprintjs/core';
import { import {
FFormGroup, FFormGroup,
@@ -11,20 +11,19 @@ import {
FDateInput, FDateInput,
FInputGroup, FInputGroup,
FTextArea, FTextArea,
FSelect,
} from '@/components'; } from '@/components';
import { useAutofocus } from '@/hooks'; import { useAutofocus } from '@/hooks';
import { import {
ListSelect,
FieldRequiredHint, FieldRequiredHint,
Col, Col,
Row, Row,
FeatureCan, FeatureCan,
BranchSelect, BranchSelect,
WarehouseSelect, WarehouseSelect,
BranchSelectButton,
FAccountsSuggestField, FAccountsSuggestField,
} from '@/components'; } from '@/components';
import { inputIntent, momentFormatter, toSafeNumber } from '@/utils'; import { momentFormatter, toSafeNumber } from '@/utils';
import { Features, CLASSES } from '@/constants'; import { Features, CLASSES } from '@/constants';
import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider'; import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider';
@@ -52,6 +51,7 @@ export default function InventoryAdjustmentFormDialogFields() {
// Inventory adjustment dialog context. // Inventory adjustment dialog context.
const { accounts, branches, warehouses } = useInventoryAdjContext(); const { accounts, branches, warehouses } = useInventoryAdjContext();
const { values, setFieldValue } = useFormikContext();
// Sets the primary warehouse to form. // Sets the primary warehouse to form.
useSetPrimaryWarehouseToForm(); useSetPrimaryWarehouseToForm();
@@ -59,6 +59,17 @@ export default function InventoryAdjustmentFormDialogFields() {
// Sets the primary branch to form. // Sets the primary branch to form.
useSetPrimaryBranchToForm(); useSetPrimaryBranchToForm();
// Handle adjustment type change.
const handleAdjustmentTypeChange = (type) => {
const result = diffQuantity(
toSafeNumber(values.quantity),
toSafeNumber(values.quantity_on_hand),
type.value,
);
setFieldValue('type', type.value);
setFieldValue('new_quantity', result);
};
return ( return (
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<Row> <Row>
@@ -66,12 +77,11 @@ export default function InventoryAdjustmentFormDialogFields() {
<Col xs={5}> <Col xs={5}>
<FormGroup <FormGroup
label={<T id={'branch'} />} label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)} fill
> >
<BranchSelect <BranchSelect
name={'branch_id'} name={'branch_id'}
branches={branches} branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FormGroup>
@@ -81,7 +91,7 @@ export default function InventoryAdjustmentFormDialogFields() {
<Col xs={5}> <Col xs={5}>
<FormGroup <FormGroup
label={<T id={'warehouse'} />} label={<T id={'warehouse'} />}
className={classNames('form-group--select-list', Classes.FILL)} fill
> >
<WarehouseSelect <WarehouseSelect
name={'warehouse_id'} name={'warehouse_id'}
@@ -122,39 +132,24 @@ export default function InventoryAdjustmentFormDialogFields() {
<Col xs={5}> <Col xs={5}>
{/*------------ Adjustment type -----------*/} {/*------------ Adjustment type -----------*/}
<Field name={'type'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FFormGroup <FFormGroup
name={'type'} name={'type'}
label={<T id={'adjustment_type'} />} label={<T id={'adjustment_type'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
fill fill
fastField
> >
<ListSelect <FSelect
name={'type'}
items={adjustmentTypes} items={adjustmentTypes}
onItemSelect={(type) => { onItemChange={handleAdjustmentTypeChange}
const result = diffQuantity(
toSafeNumber(values.quantity),
toSafeNumber(values.quantity_on_hand),
type.value,
);
setFieldValue('type', type.value);
setFieldValue('new_quantity', result);
}}
filterable={false} filterable={false}
selectedItem={value} valueAccessor={'value'}
selectedItemProp={'value'} textAccessor={'name'}
textProp={'name'}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
intent={inputIntent({ error, touched })} fastField
/> />
</FFormGroup> </FFormGroup>
)}
</Field>
</Col> </Col>
</Row> </Row>

View File

@@ -73,7 +73,6 @@ function RefundCreditNoteFormFields({
<BranchSelect <BranchSelect
name={'branch_id'} name={'branch_id'}
branches={branches} branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
/> />
</FormGroup> </FormGroup>

View File

@@ -1,19 +1,16 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { import {
FormGroup,
InputGroup,
Intent, Intent,
Classes, Classes,
Button, Button,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { FastField, Form, useFormikContext, ErrorMessage } from 'formik'; import { Form, useFormikContext } from 'formik';
import classNames from 'classnames'; import classNames from 'classnames';
import { FFormGroup, FInputGroup, FormattedMessage as T } from '@/components'; import { FFormGroup, FInputGroup, FSelect, FormattedMessage as T } from '@/components';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { inputIntent } from '@/utils'; import { FieldRequiredHint } from '@/components';
import { ListSelect, FieldRequiredHint } from '@/components';
import { useUserFormContext } from './UserFormProvider'; import { useUserFormContext } from './UserFormProvider';
import withDialogActions from '@/containers/Dialog/withDialogActions'; import withDialogActions from '@/containers/Dialog/withDialogActions';
import { compose } from '@/utils'; import { compose } from '@/utils';
@@ -68,31 +65,21 @@ function UserFormContent({
</FFormGroup> </FFormGroup>
{/* ----------- Role name ----------- */} {/* ----------- Role name ----------- */}
<FastField name={'role_id'}> <FFormGroup
{({ form, field: { value }, meta: { error, touched } }) => ( name={'role_id'}
<FormGroup
label={<T id={'roles.label.role_name'} />} label={<T id={'roles.label.role_name'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
helperText={<ErrorMessage name="role_id" />}
className={classNames(CLASSES.FILL, 'form-group--role_name')} className={classNames(CLASSES.FILL, 'form-group--role_name')}
intent={inputIntent({ error, touched })}
> >
<ListSelect <FSelect
name={'role_id'}
items={roles} items={roles}
onItemSelect={({ id }) => { valueAccessor={'id'}
form.setFieldValue('role_id', id); textAccessor={'name'}
}}
selectedItem={value}
selectedItemProp={'id'}
textProp={'name'}
// labelProp={'id '}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
intent={inputIntent({ error, touched })}
disabled={isAuth} disabled={isAuth}
/> />
</FormGroup> </FFormGroup>
)}
</FastField>
</div> </div>
<div className={CLASSES.DIALOG_FOOTER}> <div className={CLASSES.DIALOG_FOOTER}>

View File

@@ -105,11 +105,6 @@ function APAgingSummaryActionsBar({
/> />
</Popover> </Popover>
<Button
className={Classes.MINIMAL}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -106,11 +106,6 @@ function ARAgingSummaryActionsBar({
/> />
</Popover> </Popover>
<Button
className={Classes.MINIMAL}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -104,18 +104,6 @@ function BalanceSheetActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<NavbarDivider /> <NavbarDivider />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}

View File

@@ -107,18 +107,6 @@ function CashFlowStatementActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -104,18 +104,6 @@ function CustomersBalanceSummaryActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -106,18 +106,6 @@ function CustomersTransactionsActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -77,19 +77,6 @@ function GeneralLedgerActionsBar({
/> />
<NavbarDivider /> <NavbarDivider />
<Popover
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<NavbarDivider />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon="print-16" iconSize={16} />} icon={<Icon icon="print-16" iconSize={16} />}

View File

@@ -102,18 +102,6 @@ function InventoryItemDetailsActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -103,18 +103,6 @@ function InventoryValuationActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon="print-16" iconSize={16} />} icon={<Icon icon="print-16" iconSize={16} />}

View File

@@ -55,17 +55,29 @@ const InventoryValuationSheet = styled(FinancialSheet)`
`; `;
const InventoryValuationDataTable = styled(ReportDataTable)` const InventoryValuationDataTable = styled(ReportDataTable)`
--color-table-text-color: #252a31;
--color-table-total-text-color: #000;
--color-table-total-border: #bbb;
.bp4-dark & {
--color-table-text-color: var(--color-light-gray1);
--color-table-total-text-color: var(--color-light-gray4);
--color-table-total-border: var(--color-dark-gray5);
}
.table { .table {
.tbody { .tbody {
.tr .td { .tr .td {
border-bottom: 0; border-bottom: 0;
padding-top: 0.4rem; padding-top: 0.4rem;
padding-bottom: 0.4rem; padding-bottom: 0.4rem;
color: var(--color-table-text-color);
} }
.tr.row_type--TOTAL .td { .tr.row_type--TOTAL .td {
border-top: 1px solid #bbb; border-top: 1px solid var(--color-table-total-border);
border-bottom: 3px double var(--color-table-total-border);
font-weight: 500; font-weight: 500;
border-bottom: 3px double #000; color: var(--color-table-total-text-color);
} }
} }
} }

View File

@@ -78,19 +78,6 @@ function JournalActionsBar({
/> />
<NavbarDivider /> <NavbarDivider />
<Popover
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<NavbarDivider />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon="print-16" iconSize={16} />} icon={<Icon icon="print-16" iconSize={16} />}

View File

@@ -103,18 +103,6 @@ function ProfitLossActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon="print-16" iconSize={16} />} icon={<Icon icon="print-16" iconSize={16} />}

View File

@@ -93,17 +93,6 @@ function ProjectProfitabilitySummaryActionsBar({
icon={<Icon icon="numbers" width={23} height={16} />} icon={<Icon icon="numbers" width={23} height={16} />}
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<NavbarDivider /> <NavbarDivider />

View File

@@ -100,17 +100,6 @@ function PurchasesByItemsActionsBar({
icon={<Icon icon="numbers" width={23} height={16} />} icon={<Icon icon="numbers" width={23} height={16} />}
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}

View File

@@ -91,11 +91,6 @@ function RealizedGainOrLossActionsBar({
/> />
</Popover> </Popover>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -102,18 +102,6 @@ function SalesByItemsActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon="print-16" iconSize={16} />} icon={<Icon icon="print-16" iconSize={16} />}

View File

@@ -104,18 +104,6 @@ function SalesTaxLiabilitySummaryActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -103,18 +103,6 @@ function TrialBalanceActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon="print-16" iconSize={16} />} icon={<Icon icon="print-16" iconSize={16} />}

View File

@@ -52,6 +52,7 @@ function TrialBalanceSheetHeader({
fromDate: moment().toDate(), fromDate: moment().toDate(),
toDate: moment().toDate(), toDate: moment().toDate(),
branchesIds: [], branchesIds: [],
filterByOption: 'with-transactions',
}; };
// Initial values. // Initial values.

View File

@@ -92,11 +92,6 @@ function UnrealizedGainOrLossActionsBar({
/> />
</Popover> </Popover>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -105,11 +105,6 @@ function VendorsBalanceSummaryActionsBar({
/> />
</Popover> </Popover>
<Button
className={Classes.MINIMAL}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -106,18 +106,6 @@ function VendorsTransactionsActionsBar({
/> />
</Popover> </Popover>
<Popover
// content={}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter')}
text={<T id={'filter'} />}
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<NavbarDivider /> <NavbarDivider />
<Button <Button

View File

@@ -6,7 +6,8 @@ import classNames from 'classnames';
import styled from 'styled-components'; import styled from 'styled-components';
import { import {
ListSelect, FFormGroup,
FSelect,
FieldRequiredHint, FieldRequiredHint,
FormattedMessage as T, FormattedMessage as T,
} from '@/components'; } from '@/components';
@@ -16,29 +17,23 @@ import { inputIntent } from '@/utils';
export default function NotifyViaSMSFormFields({ notificationTypes }) { export default function NotifyViaSMSFormFields({ notificationTypes }) {
return ( return (
<NotifyViaSMSFormFieldsRoot> <NotifyViaSMSFormFieldsRoot>
<FastField name={'notification_key'}> <FFormGroup
{({ form, field: { value }, meta: { error, touched } }) => ( name={'notification_key'}
<FormGroup
label={<T id={'notify_via_sms.dialog.notification_type'} />} label={<T id={'notify_via_sms.dialog.notification_type'} />}
className={classNames(CLASSES.FILL)} className={classNames(CLASSES.FILL)}
intent={inputIntent({ error, touched })} fastField
helperText={<ErrorMessage name={'customer_name'} />}
> >
<ListSelect <FSelect
name={'notification_key'}
items={notificationTypes} items={notificationTypes}
selectedItemProp={'key'} valueAccessor={'key'}
selectedItem={value} textAccessor={'label'}
textProp={'label'}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
filterable={false} filterable={false}
onItemSelect={(notification) => {
form.setFieldValue('notification_key', notification.key);
}}
disabled={notificationTypes.length < 2} disabled={notificationTypes.length < 2}
fastField
/> />
</FormGroup> </FFormGroup>
)}
</FastField>
{/* ----------- Send Notification to ----------- */} {/* ----------- Send Notification to ----------- */}
<FastField name={'customer_name'}> <FastField name={'customer_name'}>

View File

@@ -0,0 +1,98 @@
import { ComponentType } from 'react';
import { Dispatch } from 'redux';
import { connect, MapStateToProps } from 'react-redux';
/**
* Creates a simple dispatch HOC that injects action props into a component.
* This is the DRY utility for Pattern 1: Action HOCs.
*
* @example
* ```tsx
* export interface WithAlertActionsProps {
* openAlert: (name: string) => void;
* }
*
* export const mapDispatchToProps = (dispatch: Dispatch): WithAlertActionsProps => ({
* openAlert: (name) => dispatch({ type: 'OPEN_ALERT', name }),
* });
*
* export const withAlertActions = createActionHOC<WithAlertActionsProps>(mapDispatchToProps);
* ```
*/
export function createActionHOC<TInjectedProps extends object>(
mapDispatchToProps: (dispatch: Dispatch) => TInjectedProps,
) {
return function withHOC<P extends TInjectedProps>(
WrappedComponent: ComponentType<P>,
): ComponentType<Omit<P, keyof TInjectedProps>> {
const Connected = connect(null, mapDispatchToProps)(
WrappedComponent as ComponentType<any>,
);
return Connected as unknown as ComponentType<Omit<P, keyof TInjectedProps>>;
};
}
/**
* Creates a state factory HOC that accepts an optional mapState function.
* This is the DRY utility for Pattern 2: State Factory HOCs.
*
* @example
* ```tsx
* export interface WithDrawersProps {
* isOpen: boolean;
* payload: Record<string, unknown>;
* }
*
* export const withDrawers = createStateFactoryHOC<WithDrawersProps>((state, props) => ({
* isOpen: isDrawerOpen(state, props),
* payload: getDrawerPayload(state, props),
* }));
* ```
*/
export function createStateFactoryHOC<TInjectedProps extends object>(
createMapState: () => (state: any, props: any) => TInjectedProps,
) {
type MapStateFn<T> = (mapped: TInjectedProps, state?: unknown, props?: unknown) => T;
return function withHOC<P, T = TInjectedProps>(mapState?: MapStateFn<T>) {
const mapStateToProps = createMapState();
const wrappedMapState = (state: any, props: any) => {
const mapped = mapStateToProps(state, props);
return mapState ? mapState(mapped, state, props) : mapped;
};
return function <C extends ComponentType<P>>(WrappedComponent: C) {
return connect(wrappedMapState)(WrappedComponent as ComponentType<any>) as unknown as ComponentType<
Omit<P, keyof (T extends TInjectedProps ? T : TInjectedProps)>
>;
};
};
}
/**
* Creates a simple state HOC that directly connects state to component props.
* This is for HOCs that don't use the factory pattern.
*
* @example
* ```tsx
* export interface WithCurrencyDetailProps {
* currency: Record<string, unknown> | null;
* }
*
* export const withCurrencyDetail = createStateHOC<WithCurrencyDetailProps>((state, props) => ({
* currency: getCurrencyByCode(state, props),
* }));
* ```
*/
export function createStateHOC<TInjectedProps extends object>(
mapStateToProps: (state: any, props: any) => TInjectedProps,
) {
return function withHOC<P extends TInjectedProps>(
WrappedComponent: ComponentType<P>,
): ComponentType<Omit<P, keyof TInjectedProps>> {
const Connected = connect(mapStateToProps)(
WrappedComponent as ComponentType<any>,
);
return Connected as unknown as ComponentType<Omit<P, keyof TInjectedProps>>;
};
}