mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 21:00:31 +00:00
feat: rewrite repositories with base entity repository class.
feat: sales and purchases status. feat: sales and purchases auto-increment number. fix: settings find query with extra columns.
This commit is contained in:
@@ -65,8 +65,8 @@ export default class JournalCommands{
|
||||
async customerOpeningBalance(customerId: number, openingBalance: number) {
|
||||
const { accountRepository } = this.repositories;
|
||||
|
||||
const openingBalanceAccount = await accountRepository.getBySlug('opening-balance');
|
||||
const receivableAccount = await accountRepository.getBySlug('accounts-receivable');
|
||||
const openingBalanceAccount = await accountRepository.findOne({ slug: 'opening-balance' });
|
||||
const receivableAccount = await accountRepository.findOne({ slug: 'accounts-receivable' });
|
||||
|
||||
const commonEntry = {
|
||||
referenceType: 'CustomerOpeningBalance',
|
||||
@@ -98,8 +98,8 @@ export default class JournalCommands{
|
||||
async vendorOpeningBalance(vendorId: number, openingBalance: number) {
|
||||
const { accountRepository } = this.repositories;
|
||||
|
||||
const payableAccount = await accountRepository.getBySlug('accounts-payable');
|
||||
const otherCost = await accountRepository.getBySlug('other-expenses');
|
||||
const payableAccount = await accountRepository.findOne({ slug: 'accounts-payable' });
|
||||
const otherCost = await accountRepository.findOne({ slug: 'other-expenses' });
|
||||
|
||||
const commonEntry = {
|
||||
referenceType: 'VendorOpeningBalance',
|
||||
|
||||
@@ -166,7 +166,7 @@ export default class JournalPoster implements IJournalPoster {
|
||||
accountsIds.map(async (account: number) => {
|
||||
const accountChange = accountsChange[account];
|
||||
const accountNode = this.accountsDepGraph.getNodeData(account);
|
||||
const accountTypeMeta = await accountTypeRepository.getTypeMeta(accountNode.accountTypeId);
|
||||
const accountTypeMeta = await accountTypeRepository.findOneById(accountNode.accountTypeId);
|
||||
const { normal }: { normal: TEntryType } = accountTypeMeta;
|
||||
let change = 0;
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ export default class AccountsService {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[accounts] validating the account existance.', { tenantId, accountId });
|
||||
const account = await accountRepository.findById(accountId);
|
||||
const account = await accountRepository.findOneById(accountId);
|
||||
|
||||
if (!account) {
|
||||
this.logger.info('[accounts] the given account not found.', { accountId });
|
||||
@@ -187,7 +187,7 @@ export default class AccountsService {
|
||||
// Inherit active status from parent account.
|
||||
accountDTO.active = parentAccount.active;
|
||||
}
|
||||
const account = await accountRepository.insert({
|
||||
const account = await accountRepository.create({
|
||||
...accountDTO,
|
||||
slug: kebabCase(accountDTO.name),
|
||||
});
|
||||
@@ -231,7 +231,10 @@ export default class AccountsService {
|
||||
this.throwErrorIfParentHasDiffType(accountDTO, parentAccount);
|
||||
}
|
||||
// Update the account on the storage.
|
||||
const account = await accountRepository.edit(oldAccount.id, accountDTO);
|
||||
const account = await accountRepository.updateAndFetch({
|
||||
id: oldAccount.id,
|
||||
...accountDTO
|
||||
});
|
||||
this.logger.info('[account] account edited successfully.', {
|
||||
account, accountDTO, tenantId
|
||||
});
|
||||
@@ -545,8 +548,8 @@ export default class AccountsService {
|
||||
|
||||
this.throwErrorIfAccountPredefined(account);
|
||||
|
||||
const accountType = await accountTypeRepository.getTypeMeta(account.accountTypeId);
|
||||
const toAccountType = await accountTypeRepository.getTypeMeta(toAccount.accountTypeId);
|
||||
const accountType = await accountTypeRepository.findOneById(account.accountTypeId);
|
||||
const toAccountType = await accountTypeRepository.findOneById(toAccount.accountTypeId);
|
||||
|
||||
if (accountType.rootType !== toAccountType.rootType) {
|
||||
throw new ServiceError('close_account_and_to_account_not_same_type');
|
||||
|
||||
@@ -27,10 +27,13 @@ export default class ContactsService {
|
||||
* @return {Promise<IContact>}
|
||||
*/
|
||||
public async getContactByIdOrThrowError(tenantId: number, contactId: number, contactService: TContactService) {
|
||||
const { Contact } = this.tenancy.models(tenantId);
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[contact] trying to validate contact existance.', { tenantId, contactId });
|
||||
const contact = await Contact.query().findById(contactId).where('contact_service', contactService);
|
||||
const contact = await contactRepository.findOne({
|
||||
id: contactId,
|
||||
contactService: contactService,
|
||||
});
|
||||
|
||||
if (!contact) {
|
||||
throw new ServiceError('contact_not_found');
|
||||
@@ -70,7 +73,7 @@ export default class ContactsService {
|
||||
const contactObj = this.transformContactObj(contactDTO);
|
||||
|
||||
this.logger.info('[contacts] trying to insert contact to the storage.', { tenantId, contactDTO });
|
||||
const contact = await contactRepository.insert({ contactService, ...contactObj });
|
||||
const contact = await contactRepository.create({ contactService, ...contactObj });
|
||||
|
||||
this.logger.info('[contacts] contact inserted successfully.', { tenantId, contact });
|
||||
return contact;
|
||||
@@ -84,13 +87,13 @@ export default class ContactsService {
|
||||
* @param {IContactDTO} contactDTO
|
||||
*/
|
||||
async editContact(tenantId: number, contactId: number, contactDTO: IContactEditDTO, contactService: TContactService) {
|
||||
const { Contact } = this.tenancy.models(tenantId);
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
const contactObj = this.transformContactObj(contactDTO);
|
||||
|
||||
const contact = await this.getContactByIdOrThrowError(tenantId, contactId, contactService);
|
||||
|
||||
this.logger.info('[contacts] trying to edit the given contact details.', { tenantId, contactId, contactDTO });
|
||||
await Contact.query().findById(contactId).patch({ ...contactObj })
|
||||
await contactRepository.update({ ...contactObj }, { id: contactId });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,6 +108,8 @@ export default class ContactsService {
|
||||
const contact = await this.getContactByIdOrThrowError(tenantId, contactId, contactService);
|
||||
|
||||
this.logger.info('[contacts] trying to delete the given contact.', { tenantId, contactId });
|
||||
|
||||
// Deletes contact of the given id.
|
||||
await contactRepository.deleteById(contactId);
|
||||
}
|
||||
|
||||
@@ -151,7 +156,7 @@ export default class ContactsService {
|
||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||
this.getContactsOrThrowErrorNotFound(tenantId, contactsIds, contactService);
|
||||
|
||||
await contactRepository.bulkDelete(contactsIds);
|
||||
await contactRepository.deleteWhereIdIn(contactsIds);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,13 +15,15 @@ import {
|
||||
ICustomersFilter,
|
||||
IContactNewDTO,
|
||||
IContactEditDTO,
|
||||
IContact
|
||||
IContact,
|
||||
ISaleInvoice
|
||||
} from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import events from 'subscribers/events';
|
||||
import moment from 'moment';
|
||||
import SaleInvoiceRepository from 'repositories/SaleInvoiceRepository';
|
||||
|
||||
@Service()
|
||||
export default class CustomersService {
|
||||
@@ -68,6 +70,7 @@ export default class CustomersService {
|
||||
}
|
||||
|
||||
private transformContactToCustomer(contactModel: IContact) {
|
||||
console.log(contactModel);
|
||||
return {
|
||||
...omit(contactModel.toJSON(), ['contactService', 'contactType']),
|
||||
customerType: contactModel.contactType,
|
||||
@@ -263,8 +266,10 @@ export default class CustomersService {
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
private async customerHasNoInvoicesOrThrowError(tenantId: number, customerId: number) {
|
||||
const { customerRepository } = this.tenancy.repositories(tenantId);
|
||||
const salesInvoice = await customerRepository.getSalesInvoices(customerId);
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve the sales invoices that assocaited to the given customer.
|
||||
const salesInvoice = await saleInvoiceRepository.find({ customer_id: customerId });
|
||||
|
||||
if (salesInvoice.length > 0) {
|
||||
throw new ServiceError('customer_has_invoices');
|
||||
@@ -279,14 +284,13 @@ export default class CustomersService {
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
private async customersHaveNoInvoicesOrThrowError(tenantId: number, customersIds: number[]) {
|
||||
const { customerRepository } = this.tenancy.repositories(tenantId);
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const customersWithInvoices = await customerRepository.customersWithSalesInvoices(
|
||||
customersIds,
|
||||
const customersInvoices = await saleInvoiceRepository.findWhereIn(
|
||||
'customer_id', customersIds
|
||||
);
|
||||
const customersIdsWithInvoice = customersWithInvoices
|
||||
.filter((customer: ICustomer) => customer.salesInvoices.length > 0)
|
||||
.map((customer: ICustomer) => customer.id);
|
||||
const customersIdsWithInvoice = customersInvoices
|
||||
.map((saleInvoice: ISaleInvoice) => saleInvoice.customerId);
|
||||
|
||||
const customersHaveInvoices = difference(customersIds, customersIdsWithInvoice);
|
||||
|
||||
|
||||
@@ -194,8 +194,10 @@ export default class VendorsService {
|
||||
* @param {number} vendorId
|
||||
*/
|
||||
private async vendorHasNoBillsOrThrowError(tenantId: number, vendorId: number) {
|
||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
||||
const bills = await vendorRepository.getBills(vendorId);
|
||||
const { billRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve the bill that associated to the given vendor id.
|
||||
const bills = await billRepository.find({ vendor_id: vendorId });
|
||||
|
||||
if (bills.length > 0) {
|
||||
throw new ServiceError('vendor_has_bills')
|
||||
@@ -209,14 +211,14 @@ export default class VendorsService {
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
private async vendorsHaveNoBillsOrThrowError(tenantId: number, vendorsIds: number[]) {
|
||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
||||
const { billRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const vendorsWithBills = await vendorRepository.vendorsWithBills(vendorsIds);
|
||||
const vendorsIdsWithBills = vendorsWithBills
|
||||
.filter((vendor: IVendor) => vendor.bills.length > 0)
|
||||
.map((vendor: IVendor) => vendor.id);
|
||||
// Retrieves bills that assocaited to the given vendors.
|
||||
const vendorsBills = await billRepository.findWhereIn('vendor_id', vendorsIds);
|
||||
const billsVendorsIds = vendorsBills.map((bill) => bill.vendorId);
|
||||
|
||||
const vendorsHaveInvoices = difference(vendorsIds, vendorsIdsWithBills);
|
||||
// The difference between the vendors ids and bills vendors ids.
|
||||
const vendorsHaveInvoices = difference(vendorsIds, billsVendorsIds);
|
||||
|
||||
if (vendorsHaveInvoices.length > 0) {
|
||||
throw new ServiceError('some_vendors_have_bills');
|
||||
|
||||
@@ -39,7 +39,7 @@ export default class DynamicListService implements IDynamicListService {
|
||||
*/
|
||||
private async getCustomViewOrThrowError(tenantId: number, viewId: number, model: IModel) {
|
||||
const { viewRepository } = this.tenancy.repositories(tenantId);
|
||||
const view = await viewRepository.getById(viewId);
|
||||
const view = await viewRepository.findOneById(viewId);
|
||||
|
||||
if (!view || view.resourceModel !== model.name) {
|
||||
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
|
||||
|
||||
@@ -15,6 +15,7 @@ import events from 'subscribers/events';
|
||||
|
||||
const ERRORS = {
|
||||
EXPENSE_NOT_FOUND: 'expense_not_found',
|
||||
EXPENSES_NOT_FOUND: 'EXPENSES_NOT_FOUND',
|
||||
PAYMENT_ACCOUNT_NOT_FOUND: 'payment_account_not_found',
|
||||
SOME_ACCOUNTS_NOT_FOUND: 'some_expenses_not_found',
|
||||
TOTAL_AMOUNT_EQUALS_ZERO: 'total_amount_equals_zero',
|
||||
@@ -48,7 +49,7 @@ export default class ExpensesService implements IExpensesService {
|
||||
this.logger.info('[expenses] trying to get the given payment account.', { tenantId, paymentAccountId });
|
||||
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const paymentAccount = await accountRepository.findById(paymentAccountId)
|
||||
const paymentAccount = await accountRepository.findOneById(paymentAccountId)
|
||||
|
||||
if (!paymentAccount) {
|
||||
this.logger.info('[expenses] the given payment account not found.', { tenantId, paymentAccountId });
|
||||
@@ -68,8 +69,8 @@ export default class ExpensesService implements IExpensesService {
|
||||
private async getExpensesAccountsOrThrowError(tenantId: number, expenseAccountsIds: number[]) {
|
||||
this.logger.info('[expenses] trying to get expenses accounts.', { tenantId, expenseAccountsIds });
|
||||
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
const storedExpenseAccounts = await Account.query().whereIn(
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const storedExpenseAccounts = await accountRepository.findWhereIn(
|
||||
'id', expenseAccountsIds,
|
||||
);
|
||||
const storedExpenseAccountsIds = storedExpenseAccounts.map((a: IAccount) => a.id);
|
||||
@@ -108,7 +109,10 @@ export default class ExpensesService implements IExpensesService {
|
||||
this.logger.info('[expenses] trying to validate expenses accounts type.', { tenantId, expensesAccounts });
|
||||
|
||||
const { accountTypeRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve accounts types of the given root type.
|
||||
const expensesTypes = await accountTypeRepository.getByRootType('expense');
|
||||
|
||||
const expensesTypesIds = expensesTypes.map(t => t.id);
|
||||
const invalidExpenseAccounts: number[] = [];
|
||||
|
||||
@@ -132,6 +136,8 @@ export default class ExpensesService implements IExpensesService {
|
||||
this.logger.info('[expenses] trying to validate payment account type.', { tenantId, paymentAccount });
|
||||
|
||||
const { accountTypeRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve account tpy eof the given key.
|
||||
const validAccountsType = await accountTypeRepository.getByKeys([
|
||||
'current_asset', 'fixed_asset',
|
||||
]);
|
||||
@@ -200,7 +206,9 @@ export default class ExpensesService implements IExpensesService {
|
||||
const { expenseRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[expense] trying to get the given expense.', { tenantId, expenseId });
|
||||
const expense = await expenseRepository.getById(expenseId);
|
||||
|
||||
// Retrieve the given expense by id.
|
||||
const expense = await expenseRepository.findOneById(expenseId);
|
||||
|
||||
if (!expense) {
|
||||
this.logger.info('[expense] the given expense not found.', { tenantId, expenseId });
|
||||
@@ -209,8 +217,27 @@ export default class ExpensesService implements IExpensesService {
|
||||
return expense;
|
||||
}
|
||||
|
||||
async getExpensesOrThrowError(tenantId: number, expensesIds: number[]) {
|
||||
/**
|
||||
* Retrieve the give expenses models or throw not found service error.
|
||||
* @param {number} tenantId -
|
||||
* @param {number[]} expensesIds -
|
||||
*/
|
||||
async getExpensesOrThrowError(
|
||||
tenantId: number,
|
||||
expensesIds: number[]
|
||||
): Promise<IExpense> {
|
||||
const { expenseRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const storedExpenses = expenseRepository.findWhereIn('id', expensesIds);
|
||||
|
||||
const storedExpensesIds = storedExpenses.map((expense) => expense.id);
|
||||
const notFoundExpenses = difference(expensesIds, storedExpensesIds);
|
||||
|
||||
if (notFoundExpenses.length > 0) {
|
||||
this.logger.info('[expense] the give expenses ids not found.', { tenantId, expensesIds });
|
||||
throw new ServiceError(ERRORS.EXPENSES_NOT_FOUND)
|
||||
}
|
||||
return storedExpenses;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,7 +328,12 @@ export default class ExpensesService implements IExpensesService {
|
||||
|
||||
// - Update the expense on the storage.
|
||||
const expenseObj = this.expenseDTOToModel(expenseDTO);
|
||||
const expenseModel = await expenseRepository.update(expenseId, expenseObj, null);
|
||||
|
||||
// - Upsert the expense object with expense entries.
|
||||
const expenseModel = await expenseRepository.upsertGraph({
|
||||
id: expenseId,
|
||||
...expenseObj,
|
||||
});
|
||||
|
||||
this.logger.info('[expense] the expense updated on the storage successfully.', { tenantId, expenseDTO });
|
||||
return expenseModel;
|
||||
@@ -348,7 +380,7 @@ export default class ExpensesService implements IExpensesService {
|
||||
|
||||
// 6. Save the expense to the storage.
|
||||
const expenseObj = this.expenseDTOToModel(expenseDTO, authorizedUser);
|
||||
const expenseModel = await expenseRepository.create(expenseObj);
|
||||
const expenseModel = await expenseRepository.upsertGraph(expenseObj);
|
||||
|
||||
this.logger.info('[expense] the expense stored to the storage successfully.', { tenantId, expenseDTO });
|
||||
|
||||
@@ -394,7 +426,7 @@ export default class ExpensesService implements IExpensesService {
|
||||
const { expenseRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[expense] trying to delete the expense.', { tenantId, expenseId });
|
||||
await expenseRepository.delete(expenseId);
|
||||
await expenseRepository.deleteById(expenseId);
|
||||
|
||||
this.logger.info('[expense] the expense deleted successfully.', { tenantId, expenseId });
|
||||
|
||||
@@ -413,7 +445,7 @@ export default class ExpensesService implements IExpensesService {
|
||||
const { expenseRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[expense] trying to delete the given expenses.', { tenantId, expensesIds });
|
||||
await expenseRepository.bulkDelete(expensesIds);
|
||||
await expenseRepository.deleteWhereIdIn(expensesIds);
|
||||
|
||||
this.logger.info('[expense] the given expenses deleted successfully.', { tenantId, expensesIds });
|
||||
|
||||
@@ -432,7 +464,7 @@ export default class ExpensesService implements IExpensesService {
|
||||
const { expenseRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[expense] trying to publish the given expenses.', { tenantId, expensesIds });
|
||||
await expenseRepository.bulkPublish(expensesIds);
|
||||
await expenseRepository.whereIdInPublish(expensesIds);
|
||||
|
||||
this.logger.info('[expense] the given expenses ids published successfully.', { tenantId, expensesIds });
|
||||
|
||||
@@ -474,13 +506,13 @@ export default class ExpensesService implements IExpensesService {
|
||||
* @return {Promise<IExpense>}
|
||||
*/
|
||||
public async getExpense(tenantId: number, expenseId: number): Promise<IExpense> {
|
||||
const { Expense } = this.tenancy.models(tenantId);
|
||||
|
||||
const expense = await Expense.query().findById(expenseId)
|
||||
.withGraphFetched('paymentAccount')
|
||||
.withGraphFetched('media')
|
||||
.withGraphFetched('categories.expenseAccount');
|
||||
const { expenseRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const expense = await expenseRepository.findOneById(expenseId, [
|
||||
'paymentAccount',
|
||||
'media',
|
||||
'categories.expenseAccount',
|
||||
]);
|
||||
if (!expense) {
|
||||
throw new ServiceError(ERRORS.EXPENSE_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export default class BalanceSheetStatementService
|
||||
this.logger.info('[balance_sheet] trying to calculate the report.', { filter, tenantId });
|
||||
|
||||
// Retrieve all accounts on the storage.
|
||||
const accounts = await accountRepository.allAccounts('type');
|
||||
const accounts = await accountRepository.all('type');
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
|
||||
// Retrieve all journal transactions based on the given query.
|
||||
|
||||
@@ -80,7 +80,7 @@ export default class GeneralLedgerService {
|
||||
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' });
|
||||
|
||||
// Retrieve all accounts from the storage.
|
||||
const accounts = await accountRepository.allAccounts('type');
|
||||
const accounts = await accountRepository.all('type');
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
|
||||
// Retreive journal transactions from/to the given date.
|
||||
|
||||
@@ -66,7 +66,7 @@ export default class ProfitLossSheetService {
|
||||
const baseCurrency = settings.get({ group: 'organization', key: 'base_currency' });
|
||||
|
||||
// Retrieve all accounts on the storage.
|
||||
const accounts = await accountRepository.allAccounts('type');
|
||||
const accounts = await accountRepository.all('type');
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
|
||||
// Retrieve all journal transactions based on the given query.
|
||||
|
||||
@@ -58,7 +58,7 @@ export default class TrialBalanceSheetService {
|
||||
this.logger.info('[trial_balance_sheet] trying to calcualte the report.', { tenantId, filter });
|
||||
|
||||
// Retrieve all accounts on the storage.
|
||||
const accounts = await accountRepository.allAccounts('type');
|
||||
const accounts = await accountRepository.all('type');
|
||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||
|
||||
// Retrieve all journal transactions based on the given query.
|
||||
|
||||
@@ -120,7 +120,7 @@ export default class ItemCategoriesService implements IItemCategoriesService {
|
||||
|
||||
this.logger.info('[items] validate sell account existance.', { tenantId, sellAccountId });
|
||||
const incomeType = await accountTypeRepository.getByKey('income');
|
||||
const foundAccount = await accountRepository.findById(sellAccountId);
|
||||
const foundAccount = await accountRepository.findOneById(sellAccountId);
|
||||
|
||||
if (!foundAccount) {
|
||||
this.logger.info('[items] sell account not found.', { tenantId, sellAccountId });
|
||||
@@ -142,7 +142,7 @@ export default class ItemCategoriesService implements IItemCategoriesService {
|
||||
|
||||
this.logger.info('[items] validate cost account existance.', { tenantId, costAccountId });
|
||||
const COGSType = await accountTypeRepository.getByKey('cost_of_goods_sold');
|
||||
const foundAccount = await accountRepository.findById(costAccountId)
|
||||
const foundAccount = await accountRepository.findOneById(costAccountId)
|
||||
|
||||
if (!foundAccount) {
|
||||
this.logger.info('[items] cost account not found.', { tenantId, costAccountId });
|
||||
@@ -164,7 +164,7 @@ export default class ItemCategoriesService implements IItemCategoriesService {
|
||||
|
||||
this.logger.info('[items] validate inventory account existance.', { tenantId, inventoryAccountId });
|
||||
const otherAsset = await accountTypeRepository.getByKey('other_asset');
|
||||
const foundAccount = await accountRepository.findById(inventoryAccountId);
|
||||
const foundAccount = await accountRepository.findOneById(inventoryAccountId);
|
||||
|
||||
if (!foundAccount) {
|
||||
this.logger.info('[items] inventory account not found.', { tenantId, inventoryAccountId });
|
||||
|
||||
@@ -85,7 +85,7 @@ export default class ItemsService implements IItemsService {
|
||||
|
||||
this.logger.info('[items] validate cost account existance.', { tenantId, costAccountId });
|
||||
const COGSType = await accountTypeRepository.getByKey('cost_of_goods_sold');
|
||||
const foundAccount = await accountRepository.findById(costAccountId)
|
||||
const foundAccount = await accountRepository.findOneById(costAccountId)
|
||||
|
||||
if (!foundAccount) {
|
||||
this.logger.info('[items] cost account not found.', { tenantId, costAccountId });
|
||||
@@ -106,7 +106,7 @@ export default class ItemsService implements IItemsService {
|
||||
|
||||
this.logger.info('[items] validate sell account existance.', { tenantId, sellAccountId });
|
||||
const incomeType = await accountTypeRepository.getByKey('income');
|
||||
const foundAccount = await accountRepository.findById(sellAccountId);
|
||||
const foundAccount = await accountRepository.findOneById(sellAccountId);
|
||||
|
||||
if (!foundAccount) {
|
||||
this.logger.info('[items] sell account not found.', { tenantId, sellAccountId });
|
||||
@@ -127,7 +127,7 @@ export default class ItemsService implements IItemsService {
|
||||
|
||||
this.logger.info('[items] validate inventory account existance.', { tenantId, inventoryAccountId });
|
||||
const otherAsset = await accountTypeRepository.getByKey('other_asset');
|
||||
const foundAccount = await accountRepository.findById(inventoryAccountId);
|
||||
const foundAccount = await accountRepository.findOneById(inventoryAccountId);
|
||||
|
||||
if (!foundAccount) {
|
||||
this.logger.info('[items] inventory account not found.', { tenantId, inventoryAccountId });
|
||||
|
||||
@@ -197,7 +197,8 @@ export default class ManualJournalsService implements IManualJournalsService {
|
||||
contactRequired: boolean = true,
|
||||
): Promise<void> {
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
const payableAccount = await accountRepository.getBySlug(accountBySlug);
|
||||
const payableAccount = await accountRepository.findOne({ slug: accountBySlug });
|
||||
|
||||
const entriesHasNoVendorContact = manualJournalDTO.entries.filter(
|
||||
(e) =>
|
||||
e.accountId === payableAccount.id &&
|
||||
|
||||
@@ -70,7 +70,9 @@ export default class BillPaymentsService {
|
||||
*/
|
||||
private async getVendorOrThrowError(tenantId: number, vendorId: number) {
|
||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
||||
const vendor = await vendorRepository.findById(vendorId);
|
||||
|
||||
// Retrieve vendor details of the given id.
|
||||
const vendor = await vendorRepository.findOneById(vendorId);
|
||||
|
||||
if (!vendor) {
|
||||
throw new ServiceError(ERRORS.BILL_VENDOR_NOT_FOUND)
|
||||
@@ -106,7 +108,7 @@ export default class BillPaymentsService {
|
||||
const { accountTypeRepository, accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const currentAssetTypes = await accountTypeRepository.getByChildType('current_asset');
|
||||
const paymentAccount = await accountRepository.findById(paymentAccountId);
|
||||
const paymentAccount = await accountRepository.findOneById(paymentAccountId);
|
||||
|
||||
const currentAssetTypesIds = currentAssetTypes.map(type => type.id);
|
||||
|
||||
@@ -405,7 +407,9 @@ export default class BillPaymentsService {
|
||||
|
||||
const paymentAmount = sumBy(billPayment.entries, 'paymentAmount');
|
||||
const formattedDate = moment(billPayment.paymentDate).format('YYYY-MM-DD');
|
||||
const payableAccount = await accountRepository.getBySlug('accounts-payable');
|
||||
|
||||
// Retrieve AP account from the storage.
|
||||
const payableAccount = await accountRepository.findOne({ slug: 'accounts-payable' });
|
||||
|
||||
const journal = new JournalPoster(tenantId);
|
||||
const commonJournal = {
|
||||
|
||||
@@ -23,13 +23,10 @@ import {
|
||||
IPaginationMeta,
|
||||
IFilterMeta,
|
||||
IBillsFilter,
|
||||
IBillPaymentEntry,
|
||||
} from 'interfaces';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
import { Bill } from 'models';
|
||||
import PaymentMadesSubscriber from 'subscribers/paymentMades';
|
||||
|
||||
const ERRORS = {
|
||||
BILL_NOT_FOUND: 'BILL_NOT_FOUND',
|
||||
@@ -39,6 +36,7 @@ const ERRORS = {
|
||||
BILL_ITEMS_NOT_FOUND: 'BILL_ITEMS_NOT_FOUND',
|
||||
BILL_ENTRIES_IDS_NOT_FOUND: 'BILL_ENTRIES_IDS_NOT_FOUND',
|
||||
NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS',
|
||||
BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -82,7 +80,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
const { vendorRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[bill] trying to get vendor.', { tenantId, vendorId });
|
||||
const foundVendor = await vendorRepository.findById(vendorId);
|
||||
const foundVendor = await vendorRepository.findOneById(vendorId);
|
||||
|
||||
if (!foundVendor) {
|
||||
this.logger.info('[bill] the given vendor not found.', { tenantId, vendorId });
|
||||
@@ -138,7 +136,12 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
*
|
||||
* @returns {IBill}
|
||||
*/
|
||||
private async billDTOToModel(tenantId: number, billDTO: IBillDTO | IBillEditDTO, oldBill?: IBill) {
|
||||
private async billDTOToModel(
|
||||
tenantId: number,
|
||||
billDTO: IBillDTO | IBillEditDTO,
|
||||
oldBill?: IBill,
|
||||
authorizedUser: ISystemUser,
|
||||
) {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
let invLotNumber = oldBill?.invLotNumber;
|
||||
|
||||
@@ -152,10 +155,22 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
const amount = sumBy(entries, 'amount');
|
||||
|
||||
return {
|
||||
...formatDateFields(billDTO, ['billDate', 'dueDate']),
|
||||
...formatDateFields(
|
||||
omit(billDTO, ['open']),
|
||||
['billDate', 'dueDate']
|
||||
),
|
||||
amount,
|
||||
invLotNumber,
|
||||
entries,
|
||||
entries: entries.map((entry) => ({
|
||||
reference_type: 'Bill',
|
||||
...omit(entry, ['amount', 'id']),
|
||||
})),
|
||||
|
||||
// Avoid rewrite the open date in edit mode when already opened.
|
||||
...(billDTO.open && (!oldBill?.openedAt)) && ({
|
||||
openedAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
userId: authorizedUser.id,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -182,7 +197,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[bill] trying to create a new bill', { tenantId, billDTO });
|
||||
const billObj = await this.billDTOToModel(tenantId, billDTO);
|
||||
const billObj = await this.billDTOToModel(tenantId, billDTO, null, authorizedUser);
|
||||
|
||||
// Retrieve vendor or throw not found service error.
|
||||
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
|
||||
@@ -197,15 +212,8 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
// Validate non-purchasable items.
|
||||
await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries);
|
||||
|
||||
const bill = await Bill.query()
|
||||
.insertGraph({
|
||||
...omit(billObj, ['entries']),
|
||||
userId: authorizedUser.id,
|
||||
entries: billDTO.entries.map((entry) => ({
|
||||
reference_type: 'Bill',
|
||||
...omit(entry, ['amount', 'id']),
|
||||
})),
|
||||
});
|
||||
// Inserts the bill graph object to the storage.
|
||||
const bill = await Bill.query().insertGraph({ ...billObj });
|
||||
|
||||
// Triggers `onBillCreated` event.
|
||||
await this.eventDispatcher.dispatch(events.bill.onCreated, {
|
||||
@@ -227,7 +235,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
* - Increment the diff amount on the given vendor id.
|
||||
* - Re-write the inventory transactions.
|
||||
* - Re-write the bill journal transactions.
|
||||
*
|
||||
* ------
|
||||
* @param {number} tenantId - The given tenant id.
|
||||
* @param {Integer} billId - The given bill id.
|
||||
* @param {IBillEditDTO} billDTO - The given new bill details.
|
||||
@@ -237,12 +245,15 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
billDTO: IBillEditDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IBill> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[bill] trying to edit bill.', { tenantId, billId });
|
||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
||||
const billObj = await this.billDTOToModel(tenantId, billDTO, oldBill);
|
||||
|
||||
// Transforms the bill DTO object to model object.
|
||||
const billObj = await this.billDTOToModel(tenantId, billDTO, oldBill, authorizedUser);
|
||||
|
||||
// Retrieve vendor details or throw not found service error.
|
||||
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
|
||||
@@ -251,19 +262,19 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
if (billDTO.billNumber) {
|
||||
await this.validateBillNumberExists(tenantId, billDTO.billNumber, billId);
|
||||
}
|
||||
// Validate the entries ids existance.
|
||||
await this.itemsEntriesService.validateEntriesIdsExistance(tenantId, billId, 'Bill', billDTO.entries);
|
||||
|
||||
// Validate the items ids existance on the storage.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, billDTO.entries);
|
||||
|
||||
// Accept the purchasable items only.
|
||||
await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries);
|
||||
|
||||
// Update the bill transaction.
|
||||
const bill = await Bill.query().upsertGraphAndFetch({
|
||||
id: billId,
|
||||
...omit(billObj, ['entries', 'invLotNumber']),
|
||||
|
||||
entries: billDTO.entries.map((entry) => ({
|
||||
reference_type: 'Bill',
|
||||
...omit(entry, ['amount']),
|
||||
}))
|
||||
...billObj,
|
||||
});
|
||||
// Triggers event `onBillEdited`.
|
||||
await this.eventDispatcher.dispatch(events.bill.onEdited, { tenantId, billId, oldBill, bill });
|
||||
@@ -280,6 +291,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
public async deleteBill(tenantId: number, billId: number) {
|
||||
const { Bill, ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the given bill or throw not found error.
|
||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
||||
|
||||
// Delete all associated bill entries.
|
||||
@@ -340,7 +352,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
const storedItems = await Item.query().whereIn('id', entriesItemsIds);
|
||||
|
||||
const storedItemsMap = new Map(storedItems.map((item) => [item.id, item]));
|
||||
const payableAccount = await accountRepository.getBySlug('accounts-payable');
|
||||
const payableAccount = await accountRepository.find({ slug: 'accounts-payable' });
|
||||
|
||||
const journal = new JournalPoster(tenantId);
|
||||
|
||||
@@ -484,4 +496,28 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the bill as open.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId
|
||||
*/
|
||||
public async openBill(
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
): Promise<void> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the given bill or throw not found error.
|
||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
||||
|
||||
if (oldBill.isOpen) {
|
||||
throw new ServiceError(ERRORS.BILL_ALREADY_OPEN);
|
||||
}
|
||||
|
||||
// Record the bill opened at on the storage.
|
||||
await Bill.query().findById(billId).patch({
|
||||
openedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ export default class PaymentReceiveService {
|
||||
const { accountTypeRepository, accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const currentAssetTypes = await accountTypeRepository.getByChildType('current_asset');
|
||||
const depositAccount = await accountRepository.findById(depositAccountId);
|
||||
const depositAccount = await accountRepository.findOneById(depositAccountId);
|
||||
|
||||
const currentAssetTypesIds = currentAssetTypes.map(type => type.id);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
import events from 'subscribers/events';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import CustomersService from 'services/Contacts/CustomersService';
|
||||
import moment from 'moment';
|
||||
|
||||
|
||||
const ERRORS = {
|
||||
@@ -19,6 +20,8 @@ const ERRORS = {
|
||||
CUSTOMER_NOT_FOUND: 'CUSTOMER_NOT_FOUND',
|
||||
SALE_ESTIMATE_NUMBER_EXISTANCE: 'SALE_ESTIMATE_NUMBER_EXISTANCE',
|
||||
ITEMS_IDS_NOT_EXISTS: 'ITEMS_IDS_NOT_EXISTS',
|
||||
SALE_ESTIMATE_ALREADY_DELIVERED: 'SALE_ESTIMATE_ALREADY_DELIVERED',
|
||||
SALE_ESTIMATE_CONVERTED_TO_INVOICE: 'SALE_ESTIMATE_CONVERTED_TO_INVOICE'
|
||||
};
|
||||
/**
|
||||
* Sale estimate service.
|
||||
@@ -80,6 +83,39 @@ export default class SaleEstimateService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform DTO object ot model object.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleEstimateDTO} saleEstimateDTO
|
||||
* @param {ISaleEstimate} oldSaleEstimate
|
||||
* @return {ISaleEstimate}
|
||||
*/
|
||||
transformDTOToModel(
|
||||
tenantId: number,
|
||||
estimateDTO: ISaleEstimateDTO,
|
||||
oldSaleEstimate?: ISaleEstimate,
|
||||
): ISaleEstimate {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
const amount = sumBy(estimateDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
|
||||
return {
|
||||
amount,
|
||||
...formatDateFields(
|
||||
omit(estimateDTO, ['delivered', 'entries']),
|
||||
['estimateDate', 'expirationDate']
|
||||
),
|
||||
entries: estimateDTO.entries.map((entry) => ({
|
||||
reference_type: 'SaleEstimate',
|
||||
...omit(entry, ['total', 'amount', 'id']),
|
||||
})),
|
||||
|
||||
// Avoid rewrite the deliver date in edit mode when already published.
|
||||
...(estimateDTO.delivered && (!oldSaleEstimate?.deliveredAt)) && ({
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new estimate with associated entries.
|
||||
* @async
|
||||
@@ -87,16 +123,16 @@ export default class SaleEstimateService {
|
||||
* @param {EstimateDTO} estimate
|
||||
* @return {Promise<ISaleEstimate>}
|
||||
*/
|
||||
public async createEstimate(tenantId: number, estimateDTO: ISaleEstimateDTO): Promise<ISaleEstimate> {
|
||||
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
|
||||
public async createEstimate(
|
||||
tenantId: number,
|
||||
estimateDTO: ISaleEstimateDTO
|
||||
): Promise<ISaleEstimate> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[sale_estimate] inserting sale estimate to the storage.');
|
||||
|
||||
const amount = sumBy(estimateDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
const estimateObj = {
|
||||
amount,
|
||||
...formatDateFields(estimateDTO, ['estimateDate', 'expirationDate']),
|
||||
};
|
||||
// Transform DTO object ot model object.
|
||||
const estimateObj = this.transformDTOToModel(tenantId, estimateDTO);
|
||||
|
||||
// Validate estimate number uniquiness on the storage.
|
||||
if (estimateDTO.estimateNumber) {
|
||||
@@ -112,13 +148,7 @@ export default class SaleEstimateService {
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, estimateDTO.entries);
|
||||
|
||||
const saleEstimate = await SaleEstimate.query()
|
||||
.upsertGraphAndFetch({
|
||||
...omit(estimateObj, ['entries']),
|
||||
entries: estimateObj.entries.map((entry) => ({
|
||||
reference_type: 'SaleEstimate',
|
||||
...omit(entry, ['total', 'amount', 'id']),
|
||||
}))
|
||||
});
|
||||
.upsertGraphAndFetch({ ...estimateObj });
|
||||
|
||||
this.logger.info('[sale_estimate] insert sale estimated success.');
|
||||
await this.eventDispatcher.dispatch(events.saleEstimate.onCreated, {
|
||||
@@ -136,15 +166,16 @@ export default class SaleEstimateService {
|
||||
* @param {EstimateDTO} estimate
|
||||
* @return {void}
|
||||
*/
|
||||
public async editEstimate(tenantId: number, estimateId: number, estimateDTO: ISaleEstimateDTO): Promise<ISaleEstimate> {
|
||||
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
|
||||
public async editEstimate(
|
||||
tenantId: number,
|
||||
estimateId: number,
|
||||
estimateDTO: ISaleEstimateDTO
|
||||
): Promise<ISaleEstimate> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
const oldSaleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId);
|
||||
|
||||
const amount = sumBy(estimateDTO.entries, (e) => ItemEntry.calcAmount(e));
|
||||
const estimateObj = {
|
||||
amount,
|
||||
...formatDateFields(estimateDTO, ['estimateDate', 'expirationDate']),
|
||||
};
|
||||
// Transform DTO object ot model object.
|
||||
const estimateObj = this.transformDTOToModel(tenantId, estimateDTO, oldSaleEstimate);
|
||||
|
||||
// Validate estimate number uniquiness on the storage.
|
||||
if (estimateDTO.estimateNumber) {
|
||||
@@ -166,11 +197,7 @@ export default class SaleEstimateService {
|
||||
const saleEstimate = await SaleEstimate.query()
|
||||
.upsertGraphAndFetch({
|
||||
id: estimateId,
|
||||
...omit(estimateObj, ['entries']),
|
||||
entries: estimateObj.entries.map((entry) => ({
|
||||
reference_type: 'SaleEstimate',
|
||||
...omit(entry, ['total', 'amount']),
|
||||
})),
|
||||
...estimateObj
|
||||
});
|
||||
|
||||
await this.eventDispatcher.dispatch(events.saleEstimate.onEdited, {
|
||||
@@ -194,6 +221,11 @@ export default class SaleEstimateService {
|
||||
// Retrieve sale estimate or throw not found service error.
|
||||
const oldSaleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId);
|
||||
|
||||
// Throw error if the sale estimate converted to sale invoice.
|
||||
if (oldSaleEstimate.convertedToInvoiceId) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_CONVERTED_TO_INVOICE);
|
||||
}
|
||||
|
||||
this.logger.info('[sale_estimate] delete sale estimate and associated entries from the storage.');
|
||||
await ItemEntry.query()
|
||||
.where('reference_id', estimateId)
|
||||
@@ -254,4 +286,70 @@ export default class SaleEstimateService {
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts estimate to invoice.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} estimateId -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async convertEstimateToInvoice(
|
||||
tenantId: number,
|
||||
estimateId: number,
|
||||
invoiceId: number,
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve details of the given sale estimate.
|
||||
const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId);
|
||||
|
||||
await SaleEstimate.query().where('id', estimateId).patch({
|
||||
convertedToInvoiceId: invoiceId,
|
||||
convertedToInvoiceAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink the converted sale estimates from the given sale invoice.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} invoiceId -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async unlinkConvertedEstimateFromInvoice(
|
||||
tenantId: number,
|
||||
invoiceId: number,
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
await SaleEstimate.query().where({
|
||||
convertedToInvoiceId: invoiceId,
|
||||
}).patch({
|
||||
convertedToInvoiceId: null,
|
||||
convertedToInvoiceAt: null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the sale estimate as delivered.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} saleEstimateId - Sale estimate id.
|
||||
*/
|
||||
public async deliverSaleEstimate(
|
||||
tenantId: number,
|
||||
saleEstimateId: number,
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve details of the given sale estimate id.
|
||||
const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, saleEstimateId);
|
||||
|
||||
// Throws error in case the sale estimate already published.
|
||||
if (saleEstimate.isDelivered) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_ALREADY_DELIVERED);
|
||||
}
|
||||
// Record the delivered at on the storage.
|
||||
await SaleEstimate.query().where('id', saleEstimateId).patch({
|
||||
deliveredAt: moment().toMySqlDateTime()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { omit, sumBy, difference, pick, chain } from 'lodash';
|
||||
import { omit, sumBy, pick, chain } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -10,7 +11,9 @@ import {
|
||||
IItemEntry,
|
||||
ISalesInvoicesFilter,
|
||||
IPaginationMeta,
|
||||
IFilterMeta
|
||||
IFilterMeta,
|
||||
ISaleInvoiceCreateDTO,
|
||||
ISaleInvoiceEditDTO,
|
||||
} from 'interfaces';
|
||||
import events from 'subscribers/events';
|
||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||
@@ -23,11 +26,13 @@ import { ServiceError } from 'exceptions';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
import CustomersService from 'services/Contacts/CustomersService';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
|
||||
|
||||
const ERRORS = {
|
||||
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
||||
SALE_INVOICE_NOT_FOUND: 'SALE_INVOICE_NOT_FOUND',
|
||||
SALE_INVOICE_ALREADY_DELIVERED: 'SALE_INVOICE_ALREADY_DELIVERED',
|
||||
ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS',
|
||||
NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS',
|
||||
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE'
|
||||
@@ -63,6 +68,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
@Inject()
|
||||
customersService: CustomersService;
|
||||
|
||||
@Inject()
|
||||
saleEstimatesService: SaleEstimateService;
|
||||
|
||||
/**
|
||||
*
|
||||
* Validate whether sale invoice number unqiue on the storage.
|
||||
@@ -101,6 +109,33 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
return saleInvoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform DTO object to model object.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {ISaleInvoiceOTD} saleInvoiceDTO - Sale invoice DTO.
|
||||
*/
|
||||
transformDTOToModel(
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO|ISaleInvoiceEditDTO,
|
||||
oldSaleInvoice?: ISaleInvoice
|
||||
): ISaleInvoice {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
|
||||
return {
|
||||
...formatDateFields(
|
||||
omit(saleInvoiceDTO, ['delivered']),
|
||||
['invoiceDate', 'dueDate']
|
||||
),
|
||||
// Avoid rewrite the deliver date in edit mode when already published.
|
||||
...(saleInvoiceDTO.delivered && (!oldSaleInvoice?.deliveredAt)) && ({
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
balance,
|
||||
paymentAmount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sale invoices and store it to the storage
|
||||
* with associated to entries and journal transactions.
|
||||
@@ -109,18 +144,16 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
* @param {ISaleInvoice} saleInvoiceDTO -
|
||||
* @return {ISaleInvoice}
|
||||
*/
|
||||
public async createSaleInvoice(tenantId: number, saleInvoiceDTO: ISaleInvoiceOTD): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
public async createSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO
|
||||
): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const invLotNumber = 1;
|
||||
|
||||
const saleInvoiceObj: ISaleInvoice = {
|
||||
...formatDateFields(saleInvoiceDTO, ['invoiceDate', 'dueDate']),
|
||||
balance,
|
||||
paymentAmount: 0,
|
||||
// invLotNumber,
|
||||
};
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = this.transformDTOToModel(tenantId, saleInvoiceDTO);
|
||||
|
||||
// Validate customer existance.
|
||||
await this.customersService.getCustomerByIdOrThrowError(tenantId, saleInvoiceDTO.customerId);
|
||||
@@ -131,6 +164,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
}
|
||||
// Validate items ids existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleInvoiceDTO.entries);
|
||||
|
||||
// Validate items should be sellable items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleInvoiceDTO.entries);
|
||||
|
||||
this.logger.info('[sale_invoice] inserting sale invoice to the storage.');
|
||||
@@ -165,11 +200,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||
|
||||
const saleInvoiceObj = {
|
||||
...formatDateFields(saleInvoiceDTO, ['invoiceDate', 'dueDate']),
|
||||
balance,
|
||||
// invLotNumber: oldSaleInvoice.invLotNumber,
|
||||
};
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = this.transformDTOToModel(tenantId, saleInvoiceDTO, oldSaleInvoice);
|
||||
|
||||
// Validate customer existance.
|
||||
await this.customersService.getCustomerByIdOrThrowError(tenantId, saleInvoiceDTO.customerId);
|
||||
@@ -203,10 +235,34 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
|
||||
saleInvoice, oldSaleInvoice, tenantId, saleInvoiceId,
|
||||
});
|
||||
|
||||
return saleInvoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliver the given sale invoice.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} saleInvoiceId - Sale invoice id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async deliverSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
): Promise<void> {
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve details of the given sale invoice id.
|
||||
const saleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||
|
||||
// Throws error in case the sale invoice already published.
|
||||
if (saleInvoice.isDelivered) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_ALREADY_DELIVERED);
|
||||
}
|
||||
// Record the delivered at on the storage.
|
||||
await saleInvoiceRepository.update({
|
||||
deliveredAt: moment().toMySqlDateTime()
|
||||
}, { id: saleInvoiceId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given sale invoice with associated entries
|
||||
* and journal transactions.
|
||||
@@ -218,6 +274,12 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||
|
||||
// Unlink the converted sale estimates from the given sale invoice.
|
||||
await this.saleEstimatesService.unlinkConvertedEstimateFromInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
);
|
||||
|
||||
this.logger.info('[sale_invoice] delete sale invoice with entries.');
|
||||
await SaleInvoice.query().where('id', saleInvoiceId).delete();
|
||||
await ItemEntry.query()
|
||||
|
||||
@@ -66,12 +66,12 @@ export default class SalesReceiptService {
|
||||
*/
|
||||
async validateReceiptDepositAccountExistance(tenantId: number, accountId: number) {
|
||||
const { accountRepository, accountTypeRepository } = this.tenancy.repositories(tenantId);
|
||||
const depositAccount = await accountRepository.findById(accountId);
|
||||
const depositAccount = await accountRepository.findOneById(accountId);
|
||||
|
||||
if (!depositAccount) {
|
||||
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_FOUND);
|
||||
}
|
||||
const depositAccountType = await accountTypeRepository.getTypeMeta(depositAccount.accountTypeId);
|
||||
const depositAccountType = await accountTypeRepository.findOneById(depositAccount.accountTypeId);
|
||||
|
||||
if (!depositAccountType || depositAccountType.childRoot === 'current_asset') {
|
||||
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET);
|
||||
|
||||
@@ -7,9 +7,9 @@ export default class SettingsStore extends MetableStoreDB {
|
||||
* Constructor method.
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
constructor(knex: Knex) {
|
||||
constructor(repository) {
|
||||
super();
|
||||
this.setExtraColumns(['group']);
|
||||
this.setModel(Setting.bindKnex(knex));
|
||||
this.setRepository(repository);
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,10 @@ export default class HasTenancyService {
|
||||
*/
|
||||
repositories(tenantId: number) {
|
||||
return this.singletonService(tenantId, 'repositories', () => {
|
||||
return tenantRepositoriesLoader(tenantId);
|
||||
const cache = this.cache(tenantId);
|
||||
const knex = this.knex(tenantId);
|
||||
|
||||
return tenantRepositoriesLoader(knex, cache);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export default class ViewsService implements IViewsService {
|
||||
const resourceModel = this.getResourceModelOrThrowError(tenantId, resourceModelName);
|
||||
|
||||
const { viewRepository } = this.tenancy.repositories(tenantId);
|
||||
return viewRepository.allByResource(resourceModel.name);
|
||||
return viewRepository.allByResource(resourceModel.name, ['columns', 'roles']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +104,7 @@ export default class ViewsService implements IViewsService {
|
||||
const { viewRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[view] trying to get view from storage.', { tenantId, viewId });
|
||||
const view = await viewRepository.getById(viewId);
|
||||
const view = await viewRepository.findOneById(viewId);
|
||||
|
||||
if (!view) {
|
||||
this.logger.info('[view] view not found.', { tenantId, viewId });
|
||||
@@ -191,7 +191,7 @@ export default class ViewsService implements IViewsService {
|
||||
}
|
||||
// Save view details.
|
||||
this.logger.info('[views] trying to insert to storage.', { tenantId, viewDTO })
|
||||
const view = await viewRepository.insert({
|
||||
const view = await viewRepository.create({
|
||||
predefined: false,
|
||||
name: viewDTO.name,
|
||||
rolesLogicExpression: viewDTO.logicExpression,
|
||||
@@ -245,7 +245,8 @@ export default class ViewsService implements IViewsService {
|
||||
}
|
||||
// Update view details.
|
||||
this.logger.info('[views] trying to update view details.', { tenantId, viewId });
|
||||
const view = await viewRepository.update(viewId, {
|
||||
const view = await viewRepository.upsertGraph({
|
||||
id: viewId,
|
||||
predefined: false,
|
||||
name: viewEditDTO.name,
|
||||
rolesLogicExpression: viewEditDTO.logicExpression,
|
||||
|
||||
Reference in New Issue
Block a user