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:
Ahmed Bouhuolia
2020-12-13 19:50:59 +02:00
parent e9e4ddaee0
commit 188e411f02
78 changed files with 1634 additions and 869 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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