mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
441e27581b | ||
|
|
e0d9a56a29 | ||
|
|
5a017104ce | ||
|
|
25ca620836 | ||
|
|
5a3655e093 | ||
|
|
49c2777587 | ||
|
|
a5680c08c2 | ||
|
|
d909dad1bf | ||
|
|
f32cc752ef | ||
|
|
a7f98201cc | ||
|
|
a1d0fc3f0a | ||
|
|
11575cfb96 |
@@ -6,4 +6,5 @@ export default registerAs('s3', () => ({
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
bucket: process.env.S3_BUCKET,
|
||||
forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true',
|
||||
}));
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ToNumber } from '@/common/decorators/Validators';
|
||||
|
||||
class BankRuleConditionDto {
|
||||
@IsNotEmpty()
|
||||
@IsIn(['description', 'amount'])
|
||||
@IsIn(['description', 'amount', 'payee'])
|
||||
field: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
|
||||
@@ -15,8 +15,13 @@ export const RecognizeUncategorizedTransactionsJob =
|
||||
export const RecognizeUncategorizedTransactionsQueue =
|
||||
'recognize-uncategorized-transactions-queue';
|
||||
|
||||
|
||||
export interface RecognizeUncategorizedTransactionsJobPayload extends TenantJobPayload {
|
||||
ruleId: number,
|
||||
transactionsCriteria: any;
|
||||
transactionsCriteria?: RecognizeTransactionsCriteria;
|
||||
/**
|
||||
* When true, first reverts recognized transactions before recognizing again.
|
||||
* Used when a bank rule is edited to ensure transactions previously recognized
|
||||
* by lower-priority rules are re-evaluated against the updated rule.
|
||||
*/
|
||||
shouldRevert?: boolean;
|
||||
}
|
||||
@@ -93,6 +93,10 @@ export class RecognizeTranasctionsService {
|
||||
q.whereIn('id', rulesIds);
|
||||
}
|
||||
q.withGraphFetched('conditions');
|
||||
|
||||
// Order by the 'order' field to ensure higher priority rules (lower order values)
|
||||
// are matched first.
|
||||
q.orderBy('order', 'asc');
|
||||
});
|
||||
|
||||
const bankRulesByAccountId = transformToMapBy(
|
||||
|
||||
@@ -69,10 +69,13 @@ export class TriggerRecognizedTransactionsSubscriber {
|
||||
const tenantPayload = await this.tenancyContect.getTenantJobPayload();
|
||||
const payload = {
|
||||
ruleId: bankRule.id,
|
||||
shouldRevert: true,
|
||||
...tenantPayload,
|
||||
} as RecognizeUncategorizedTransactionsJobPayload;
|
||||
|
||||
// Re-recognize the transactions based on the new rules.
|
||||
// Setting shouldRevert to true ensures that transactions previously recognized
|
||||
// by this or lower-priority rules are re-evaluated against the updated rule.
|
||||
await this.recognizeTransactionsQueue.add(
|
||||
RecognizeUncategorizedTransactionsJob,
|
||||
payload,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { ClsService, UseCls } from 'nestjs-cls';
|
||||
import { RecognizeTranasctionsService } from '../commands/RecognizeTranasctions.service';
|
||||
import { RevertRecognizedTransactionsService } from '../commands/RevertRecognizedTransactions.service';
|
||||
import {
|
||||
RecognizeUncategorizedTransactionsJobPayload,
|
||||
RecognizeUncategorizedTransactionsQueue,
|
||||
@@ -15,10 +16,12 @@ import {
|
||||
export class RegonizeTransactionsPrcessor extends WorkerHost {
|
||||
/**
|
||||
* @param {RecognizeTranasctionsService} recognizeTranasctionsService -
|
||||
* @param {RevertRecognizedTransactionsService} revertRecognizedTransactionsService -
|
||||
* @param {ClsService} clsService -
|
||||
*/
|
||||
constructor(
|
||||
private readonly recognizeTranasctionsService: RecognizeTranasctionsService,
|
||||
private readonly revertRecognizedTransactionsService: RevertRecognizedTransactionsService,
|
||||
private readonly clsService: ClsService,
|
||||
) {
|
||||
super();
|
||||
@@ -29,12 +32,21 @@ export class RegonizeTransactionsPrcessor extends WorkerHost {
|
||||
*/
|
||||
@UseCls()
|
||||
async process(job: Job<RecognizeUncategorizedTransactionsJobPayload>) {
|
||||
const { ruleId, transactionsCriteria } = job.data;
|
||||
const { ruleId, transactionsCriteria, shouldRevert } = job.data;
|
||||
|
||||
this.clsService.set('organizationId', job.data.organizationId);
|
||||
this.clsService.set('userId', job.data.userId);
|
||||
|
||||
try {
|
||||
// If shouldRevert is true, first revert recognized transactions before re-recognizing.
|
||||
// This is used when a bank rule is edited to ensure transactions previously recognized
|
||||
// by lower-priority rules are re-evaluated against the updated rule.
|
||||
if (shouldRevert) {
|
||||
await this.revertRecognizedTransactionsService.revertRecognizedTransactions(
|
||||
ruleId,
|
||||
transactionsCriteria,
|
||||
);
|
||||
}
|
||||
await this.recognizeTranasctionsService.recognizeTransactions(
|
||||
ruleId,
|
||||
transactionsCriteria,
|
||||
|
||||
@@ -62,10 +62,11 @@ export class TaxRatesApplication {
|
||||
|
||||
/**
|
||||
* Retrieves the tax rates list.
|
||||
* @returns {Promise<ITaxRate[]>}
|
||||
* @returns {Promise<{ data: ITaxRate[] }>}
|
||||
*/
|
||||
public getTaxRates() {
|
||||
return this.getTaxRatesService.getTaxRates();
|
||||
public async getTaxRates() {
|
||||
const taxRates = await this.getTaxRatesService.getTaxRates();
|
||||
return { data: taxRates };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -85,9 +85,14 @@ export class TaxRatesController {
|
||||
status: 200,
|
||||
description: 'The tax rates have been successfully retrieved.',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: getSchemaPath(TaxRateResponseDto),
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: getSchemaPath(TaxRateResponseDto),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ToNumber } from '@/common/decorators/Validators';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
@@ -30,6 +31,7 @@ export class CommandTaxRateDto {
|
||||
*/
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
@ToNumber()
|
||||
@ApiProperty({
|
||||
description: 'The rate of the tax rate.',
|
||||
example: 10,
|
||||
|
||||
@@ -17,8 +17,8 @@ const Schema = Yup.object().shape({
|
||||
.label(intl.get('display_name_')),
|
||||
|
||||
email: Yup.string().email().nullable(),
|
||||
work_phone: Yup.number(),
|
||||
personal_phone: Yup.number(),
|
||||
work_phone: Yup.string().nullable(),
|
||||
personal_phone: Yup.string().nullable(),
|
||||
website: Yup.string().url().nullable(),
|
||||
|
||||
active: Yup.boolean(),
|
||||
@@ -30,7 +30,7 @@ const Schema = Yup.object().shape({
|
||||
billing_address_city: Yup.string().trim(),
|
||||
billing_address_state: Yup.string().trim(),
|
||||
billing_address_postcode: Yup.string().nullable(),
|
||||
billing_address_phone: Yup.number(),
|
||||
billing_address_phone: Yup.string().nullable(),
|
||||
|
||||
shipping_address_country: Yup.string().trim(),
|
||||
shipping_address_1: Yup.string().trim(),
|
||||
@@ -38,7 +38,7 @@ const Schema = Yup.object().shape({
|
||||
shipping_address_city: Yup.string().trim(),
|
||||
shipping_address_state: Yup.string().trim(),
|
||||
shipping_address_postcode: Yup.string().nullable(),
|
||||
shipping_address_phone: Yup.number(),
|
||||
shipping_address_phone: Yup.string().nullable(),
|
||||
|
||||
opening_balance: Yup.number().nullable(),
|
||||
currency_code: Yup.string(),
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useDrawerActions } from '@/hooks/state';
|
||||
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
|
||||
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
|
||||
import { Box } from '@/components';
|
||||
|
||||
export function CreditNoteCustomizeContent() {
|
||||
const { payload, name } = useDrawerContext();
|
||||
@@ -45,7 +46,9 @@ function CreditNoteCustomizeFormContent() {
|
||||
return (
|
||||
<ElementCustomizeContent>
|
||||
<ElementCustomize.PaperTemplate>
|
||||
<CreditNotePaperTemplateFormConnected />
|
||||
<Box overflow="auto" flex="1 1" px={4} py={6}>
|
||||
<CreditNotePaperTemplateFormConnected />
|
||||
</Box>
|
||||
</ElementCustomize.PaperTemplate>
|
||||
|
||||
<ElementCustomize.FieldsTab id={'general'} label={'General'}>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useDrawerActions } from '@/hooks/state';
|
||||
import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm';
|
||||
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
|
||||
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
|
||||
import { Box } from '@/components';
|
||||
|
||||
export function EstimateCustomizeContent() {
|
||||
const { payload, name } = useDrawerContext();
|
||||
@@ -44,7 +45,9 @@ function EstimateCustomizeFormContent() {
|
||||
return (
|
||||
<ElementCustomizeContent>
|
||||
<ElementCustomize.PaperTemplate>
|
||||
<EstimatePaperTemplateFormConnected />
|
||||
<Box overflow="auto" flex="1 1" px={4} py={6}>
|
||||
<EstimatePaperTemplateFormConnected />
|
||||
</Box>
|
||||
</ElementCustomize.PaperTemplate>
|
||||
|
||||
<ElementCustomize.FieldsTab id={'general'} label={'General'}>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useDrawerActions } from '@/hooks/state';
|
||||
import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm';
|
||||
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
|
||||
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
|
||||
import { Box } from '@/components';
|
||||
|
||||
export function PaymentReceivedCustomizeContent() {
|
||||
const { payload, name } = useDrawerContext();
|
||||
@@ -51,7 +52,9 @@ function PaymentReceivedCustomizeFormContent() {
|
||||
return (
|
||||
<ElementCustomizeContent>
|
||||
<ElementCustomize.PaperTemplate>
|
||||
<PaymentReceivedPaperTemplateFormConnected />
|
||||
<Box overflow="auto" flex="1 1" px={4} py={6}>
|
||||
<PaymentReceivedPaperTemplateFormConnected />
|
||||
</Box>
|
||||
</ElementCustomize.PaperTemplate>
|
||||
|
||||
<ElementCustomize.FieldsTab id={'general'} label={'General'}>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useDrawerActions } from '@/hooks/state';
|
||||
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||
import { useElementCustomizeContext } from '@/containers/ElementCustomize/ElementCustomizeProvider';
|
||||
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
|
||||
import { Box } from '@/components';
|
||||
|
||||
export function ReceiptCustomizeContent() {
|
||||
const { payload, name } = useDrawerContext();
|
||||
@@ -44,7 +45,9 @@ function ReceiptCustomizeFormContent() {
|
||||
return (
|
||||
<ElementCustomizeContent>
|
||||
<ElementCustomize.PaperTemplate>
|
||||
<ReceiptPaperTemplateFormConnected />
|
||||
<Box overflow="auto" flex="1 1" px={4} py={6}>
|
||||
<ReceiptPaperTemplateFormConnected />
|
||||
</Box>
|
||||
</ElementCustomize.PaperTemplate>
|
||||
|
||||
<ElementCustomize.FieldsTab id={'general'} label={'General'}>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Intent, Tag } from '@blueprintjs/core';
|
||||
import { Intent, Tag, Classes } from '@blueprintjs/core';
|
||||
import { Align } from '@/constants';
|
||||
import styled from 'styled-components';
|
||||
import clsx from 'classnames';
|
||||
|
||||
const codeAccessor = (taxRate) => {
|
||||
return (
|
||||
@@ -28,13 +28,17 @@ const nameAccessor = (taxRate) => {
|
||||
return (
|
||||
<>
|
||||
<span>{taxRate.name}</span>
|
||||
{!!taxRate.is_compound && <CompoundText>(Compound tax)</CompoundText>}
|
||||
{!!taxRate.is_compound && (
|
||||
<span className={clsx(Classes.TEXT_MUTED)}>(Compound tax)</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DescriptionAccessor = (taxRate) => {
|
||||
return <DescriptionText>{taxRate.description}</DescriptionText>;
|
||||
return (
|
||||
<span className={clsx(Classes.TEXT_MUTED)}>{taxRate.description}</span>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -72,11 +76,3 @@ export const useTaxRatesTableColumns = () => {
|
||||
];
|
||||
};
|
||||
|
||||
const CompoundText = styled('span')`
|
||||
color: #738091;
|
||||
margin-left: 5px;
|
||||
`;
|
||||
|
||||
const DescriptionText = styled('span')`
|
||||
color: #5f6b7c;
|
||||
`;
|
||||
|
||||
@@ -74,9 +74,13 @@ const TaxRateHeader = styled(`div`)`
|
||||
const TaxRateAmount = styled('div')`
|
||||
line-height: 1;
|
||||
font-size: 30px;
|
||||
color: #565b71;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
color: var(--x-color-amount-text, #565b71);
|
||||
|
||||
.bp4-dark & {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
`;
|
||||
|
||||
const TaxRateActiveTag = styled(Tag)`
|
||||
|
||||
@@ -10,8 +10,8 @@ const Schema = Yup.object().shape({
|
||||
display_name: Yup.string().trim().required().label(intl.get('display_name_')),
|
||||
|
||||
email: Yup.string().email().nullable(),
|
||||
work_phone: Yup.number(),
|
||||
personal_phone: Yup.number(),
|
||||
work_phone: Yup.string().nullable(),
|
||||
personal_phone: Yup.string().nullable(),
|
||||
website: Yup.string().url().nullable(),
|
||||
|
||||
active: Yup.boolean(),
|
||||
@@ -23,7 +23,7 @@ const Schema = Yup.object().shape({
|
||||
billing_address_city: Yup.string().trim(),
|
||||
billing_address_state: Yup.string().trim(),
|
||||
billing_address_postcode: Yup.string().nullable(),
|
||||
billing_address_phone: Yup.number(),
|
||||
billing_address_phone: Yup.string().nullable(),
|
||||
|
||||
shipping_address_country: Yup.string().trim(),
|
||||
shipping_address_1: Yup.string().trim(),
|
||||
@@ -31,7 +31,7 @@ const Schema = Yup.object().shape({
|
||||
shipping_address_city: Yup.string().trim(),
|
||||
shipping_address_state: Yup.string().trim(),
|
||||
shipping_address_postcode: Yup.string().nullable(),
|
||||
shipping_address_phone: Yup.number(),
|
||||
shipping_address_phone: Yup.string().nullable(),
|
||||
|
||||
opening_balance: Yup.number().nullable(),
|
||||
currency_code: Yup.string(),
|
||||
|
||||
@@ -37,10 +37,10 @@ export function useTaxRate(taxRateId: string, props) {
|
||||
[QUERY_TYPES.TAX_RATES, taxRateId],
|
||||
{
|
||||
method: 'get',
|
||||
url: `tax-rates/${taxRateId}}`,
|
||||
url: `tax-rates/${taxRateId}`,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data.data,
|
||||
select: (res) => res.data,
|
||||
...props,
|
||||
},
|
||||
);
|
||||
@@ -106,7 +106,7 @@ export function useActivateTaxRate(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation((id) => apiRequest.post(`tax-rates/${id}/active`), {
|
||||
return useMutation((id) => apiRequest.put(`tax-rates/${id}/activate`), {
|
||||
onSuccess: (res, id) => {
|
||||
commonInvalidateQueries(queryClient);
|
||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||
@@ -122,7 +122,7 @@ export function useInactivateTaxRate(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation((id) => apiRequest.post(`tax-rates/${id}/inactive`), {
|
||||
return useMutation((id) => apiRequest.put(`tax-rates/${id}/inactivate`), {
|
||||
onSuccess: (res, id) => {
|
||||
commonInvalidateQueries(queryClient);
|
||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||
|
||||
Reference in New Issue
Block a user