Compare commits

...

3 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
12740223a8 fix: Invalid bill payment amount on editing bill payment 2024-02-05 22:38:56 +02:00
Ahmed Bouhuolia
7b5287ee80 fix(server): Revert the paid amount to bill transaction after editing bill payment amount 2024-02-05 18:50:34 +02:00
Ahmed Bouhuolia
ba3ea93a2d chore: dump CHANGELOG.md 2024-01-30 00:28:23 +02:00
17 changed files with 83 additions and 48 deletions

View File

@@ -2,6 +2,14 @@
All notable changes to Bigcapital server-side will be in this file. All notable changes to Bigcapital server-side will be in this file.
## [0.14.0] - 30-01-2024
* feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327
* fix: expense amounts should not be rounded by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/339
* feat: get latest exchange rate from third party services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/340
* fix(webapp): inconsistency in currency of universal search items by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/335
* hotfix: editing sales and expense transactions don't reflect GL entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/342
## [0.13.3] - 22-01-2024 ## [0.13.3] - 22-01-2024
* hotfix(server): Unhandled thrown errors of services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/329 * hotfix(server): Unhandled thrown errors of services by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/329

View File

@@ -560,6 +560,16 @@ export default class BillsController extends BaseController {
errors: [{ type: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', code: 1900 }], errors: [{ type: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', code: 1900 }],
}); });
} }
if (error.errorType === 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT') {
return res.boom.badRequest(null, {
errors: [
{
type: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT',
code: 2000,
},
],
});
}
} }
next(error); next(error);
} }

View File

@@ -10,7 +10,6 @@ import {
import TenancyService from '@/services/Tenancy/TenancyService'; import TenancyService from '@/services/Tenancy/TenancyService';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { ACCOUNT_TYPE } from '@/data/AccountTypes'; import { ACCOUNT_TYPE } from '@/data/AccountTypes';
import { BillPayment } from '@/models';
import { ERRORS } from './constants'; import { ERRORS } from './constants';
@Service() @Service()
@@ -18,19 +17,6 @@ export class BillPaymentValidators {
@Inject() @Inject()
private tenancy: TenancyService; private tenancy: TenancyService;
/**
* Validates the payment existance.
* @param {BillPayment | undefined | null} payment
* @throws {ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND)}
*/
public async validateBillPaymentExistance(
payment: BillPayment | undefined | null
) {
if (!payment) {
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
}
}
/** /**
* Validates the bill payment existance. * Validates the bill payment existance.
* @param {Request} req * @param {Request} req

View File

@@ -1,6 +1,5 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import UnitOfWork from '@/services/UnitOfWork'; import UnitOfWork from '@/services/UnitOfWork';
import { BillPaymentValidators } from './BillPaymentValidators';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
@@ -21,9 +20,6 @@ export class DeleteBillPayment {
@Inject() @Inject()
private uow: UnitOfWork; private uow: UnitOfWork;
@Inject()
private validators: BillPaymentValidators;
/** /**
* Deletes the bill payment and associated transactions. * Deletes the bill payment and associated transactions.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
@@ -36,10 +32,8 @@ export class DeleteBillPayment {
// Retrieve the bill payment or throw not found service error. // Retrieve the bill payment or throw not found service error.
const oldBillPayment = await BillPayment.query() const oldBillPayment = await BillPayment.query()
.withGraphFetched('entries') .withGraphFetched('entries')
.findById(billPaymentId); .findById(billPaymentId)
.throwIfNotFound();
// Validates the bill payment existance.
this.validators.validateBillPaymentExistance(oldBillPayment);
// Deletes the bill transactions with associated transactions under // Deletes the bill transactions with associated transactions under
// unit-of-work envirement. // unit-of-work envirement.

View File

@@ -57,12 +57,12 @@ export class EditBillPayment {
const tenantMeta = await TenantMetadata.query().findOne({ tenantId }); const tenantMeta = await TenantMetadata.query().findOne({ tenantId });
const oldBillPayment = await BillPayment.query().findById(billPaymentId); const oldBillPayment = await BillPayment.query()
.findById(billPaymentId)
.withGraphFetched('entries')
.throwIfNotFound();
// Validates the bill payment existance. // Retrieves the bill payment vendor or throw not found error.
this.validators.validateBillPaymentExistance(oldBillPayment);
//
const vendor = await Contact.query() const vendor = await Contact.query()
.modify('vendor') .modify('vendor')
.findById(billPaymentDTO.vendorId) .findById(billPaymentDTO.vendorId)
@@ -126,7 +126,7 @@ export class EditBillPayment {
trx, trx,
} as IBillPaymentEditingPayload); } as IBillPaymentEditingPayload);
// Deletes the bill payment transaction graph from the storage. // Edits the bill payment transaction graph on the storage.
const billPayment = await BillPayment.query(trx).upsertGraphAndFetch({ const billPayment = await BillPayment.query(trx).upsertGraphAndFetch({
id: billPaymentId, id: billPaymentId,
...billPaymentObj, ...billPaymentObj,

View File

@@ -1,12 +1,8 @@
import { IBillPayment } from '@/interfaces'; import { IBillPayment } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { ERRORS } from './constants';
import { ServiceError } from '@/exceptions';
import { BillPaymentTransformer } from './BillPaymentTransformer'; import { BillPaymentTransformer } from './BillPaymentTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { BillsValidators } from '../Bills/BillsValidators';
import { BillPaymentValidators } from './BillPaymentValidators';
@Service() @Service()
export class GetBillPayment { export class GetBillPayment {
@@ -16,9 +12,6 @@ export class GetBillPayment {
@Inject() @Inject()
private transformer: TransformerInjectable; private transformer: TransformerInjectable;
@Inject()
private validators: BillPaymentValidators;
/** /**
* Retrieve bill payment. * Retrieve bill payment.
* @param {number} tenantId * @param {number} tenantId
@@ -37,10 +30,8 @@ export class GetBillPayment {
.withGraphFetched('paymentAccount') .withGraphFetched('paymentAccount')
.withGraphFetched('transactions') .withGraphFetched('transactions')
.withGraphFetched('branch') .withGraphFetched('branch')
.findById(billPyamentId); .findById(billPyamentId)
.throwIfNotFound();
// Validates the bill payment existance.
this.validators.validateBillPaymentExistance(billPayment);
return this.transformer.transform( return this.transformer.transform(
tenantId, tenantId,

View File

@@ -18,10 +18,9 @@ export class GetPaymentBills {
public async getPaymentBills(tenantId: number, billPaymentId: number) { public async getPaymentBills(tenantId: number, billPaymentId: number) {
const { Bill, BillPayment } = this.tenancy.models(tenantId); const { Bill, BillPayment } = this.tenancy.models(tenantId);
const billPayment = await BillPayment.query().findById(billPaymentId); const billPayment = await BillPayment.query()
.findById(billPaymentId)
// Validates the bill payment existance. .throwIfNotFound();
this.validators.validateBillPaymentExistance(billPayment);
const paymentBillsIds = billPayment.entries.map((entry) => entry.id); const paymentBillsIds = billPayment.entries.map((entry) => entry.id);

View File

@@ -21,6 +21,20 @@ export class BillsValidators {
} }
} }
/**
* Validates the bill amount is bigger than paid amount.
* @param {number} billAmount
* @param {number} paidAmount
*/
public validateBillAmountBiggerPaidAmount(
billAmount: number,
paidAmount: number,
) {
if (billAmount < paidAmount) {
throw new ServiceError(ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT);
}
}
/** /**
* Validates the bill number existance. * Validates the bill number existance.
*/ */

View File

@@ -103,6 +103,7 @@ export class EditBill {
tenantId, tenantId,
billDTO.entries billDTO.entries
); );
// Transforms the bill DTO to model object. // Transforms the bill DTO to model object.
const billObj = await this.transformerDTO.billDTOToModel( const billObj = await this.transformerDTO.billDTOToModel(
tenantId, tenantId,
@@ -111,6 +112,11 @@ export class EditBill {
authorizedUser, authorizedUser,
oldBill oldBill
); );
// Validate bill total amount should be bigger than paid amount.
this.validators.validateBillAmountBiggerPaidAmount(
billObj.amount,
oldBill.paymentAmount
);
// Validate landed cost entries that have allocated cost could not be deleted. // Validate landed cost entries that have allocated cost could not be deleted.
await this.entriesService.validateLandedCostEntriesNotDeleted( await this.entriesService.validateLandedCostEntriesNotDeleted(
oldBill.entries, oldBill.entries,

View File

@@ -18,6 +18,7 @@ export const ERRORS = {
LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS: LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS:
'LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS', 'LANDED_COST_ENTRIES_SHOULD_BE_INVENTORY_ITEMS',
BILL_HAS_APPLIED_TO_VENDOR_CREDIT: 'BILL_HAS_APPLIED_TO_VENDOR_CREDIT', BILL_HAS_APPLIED_TO_VENDOR_CREDIT: 'BILL_HAS_APPLIED_TO_VENDOR_CREDIT',
BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT',
}; };
export const DEFAULT_VIEW_COLUMNS = []; export const DEFAULT_VIEW_COLUMNS = [];

View File

@@ -61,7 +61,8 @@ export class EditPaymentReceive {
// Validate the payment receive existance. // Validate the payment receive existance.
const oldPaymentReceive = await PaymentReceive.query() const oldPaymentReceive = await PaymentReceive.query()
.withGraphFetched('entries') .withGraphFetched('entries')
.findById(paymentReceiveId); .findById(paymentReceiveId)
.throwIfNotFound();
// Validates the payment existance. // Validates the payment existance.
this.validators.validatePaymentExistance(oldPaymentReceive); this.validators.validatePaymentExistance(oldPaymentReceive);

View File

@@ -9,6 +9,8 @@ export const ERROR = {
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE', SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE',
SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE: SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE:
'SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE', 'SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE',
INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT:
'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT',
// Sales Receipts // Sales Receipts
SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE', SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE',
@@ -17,6 +19,6 @@ export const ERROR = {
// Bills // Bills
BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS', BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS',
SALE_INVOICE_NO_IS_REQUIRED: 'SALE_INVOICE_NO_IS_REQUIRED', SALE_INVOICE_NO_IS_REQUIRED: 'SALE_INVOICE_NO_IS_REQUIRED',
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:"ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED", ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
}; };

View File

@@ -67,6 +67,7 @@ export const ERRORS = {
BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS', BILL_NUMBER_EXISTS: 'BILL.NUMBER.EXISTS',
ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED: ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED:
'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED', 'ENTRIES_ALLOCATED_COST_COULD_NOT_DELETED',
BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT: 'BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT',
}; };
/** /**
* Transformes the bill to initial values of edit form. * Transformes the bill to initial values of edit form.
@@ -200,6 +201,14 @@ export const handleErrors = (errors, { setErrors }) => {
}), }),
); );
} }
if (
errors.some((e) => e.type === ERRORS.BILL_AMOUNT_SMALLER_THAN_PAID_AMOUNT)
) {
AppToaster.show({
intent: Intent.DANGER,
message: intl.get('bill.total_smaller_than_paid_amount'),
});
}
}; };
export const useSetPrimaryBranchToForm = () => { export const useSetPrimaryBranchToForm = () => {

View File

@@ -30,6 +30,7 @@ export default function PaymentMadeEntriesTable({
// Formik context. // Formik context.
const { const {
values: { vendor_id }, values: { vendor_id },
errors,
} = useFormikContext(); } = useFormikContext();
// Handle update data. // Handle update data.
@@ -63,7 +64,7 @@ export default function PaymentMadeEntriesTable({
data={entries} data={entries}
spinnerProps={false} spinnerProps={false}
payload={{ payload={{
errors: [], errors: errors?.entries || [],
updateData: handleUpdateData, updateData: handleUpdateData,
currencyCode, currencyCode,
}} }}

View File

@@ -112,6 +112,16 @@ export const transformErrors = (errors, { setErrors }) => {
intent: Intent.DANGER, intent: Intent.DANGER,
}); });
} }
if (
errors.some(
({ type }) => type === ERROR.INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT,
)
) {
AppToaster.show({
message: intl.get('sale_invoice.total_smaller_than_paid_amount'),
intent: Intent.DANGER,
});
}
if ( if (
errors.some((error) => error.type === ERROR.SALE_INVOICE_NO_IS_REQUIRED) errors.some((error) => error.type === ERROR.SALE_INVOICE_NO_IS_REQUIRED)
) { ) {

View File

@@ -28,6 +28,7 @@ export default function PaymentReceiveItemsTable({
// Formik context. // Formik context.
const { const {
values: { customer_id }, values: { customer_id },
errors,
} = useFormikContext(); } = useFormikContext();
// No results message. // No results message.
@@ -58,7 +59,7 @@ export default function PaymentReceiveItemsTable({
data={entries} data={entries}
spinnerProps={false} spinnerProps={false}
payload={{ payload={{
errors: [], errors: errors?.entries || [],
updateData: handleUpdateData, updateData: handleUpdateData,
currencyCode, currencyCode,
}} }}

View File

@@ -653,7 +653,9 @@
"invoice_number_is_not_unqiue": "Invoice number is not unqiue", "invoice_number_is_not_unqiue": "Invoice number is not unqiue",
"sale_receipt_number_not_unique": "Receipt number is not unique", "sale_receipt_number_not_unique": "Receipt number is not unique",
"sale_invoice_number_is_exists": "Sale invoice number is exists", "sale_invoice_number_is_exists": "Sale invoice number is exists",
"sale_invoice.total_smaller_than_paid_amount": "The invoice total is smaller than the invoice paid amount.",
"bill_number_exists": "Bill number exists", "bill_number_exists": "Bill number exists",
"bill.total_smaller_than_paid_amount": "The bill total is smaller than the bill paid amount.",
"ok": "Ok!", "ok": "Ok!",
"quantity_cannot_be_zero_or_empty": "Quantity cannot be zero or empty.", "quantity_cannot_be_zero_or_empty": "Quantity cannot be zero or empty.",
"customer_email": "Customer email", "customer_email": "Customer email",