- feat: Optimize tenancy software architecture.

This commit is contained in:
Ahmed Bouhuolia
2020-08-30 22:11:14 +02:00
parent 74321a2886
commit ca251a2d28
53 changed files with 1581 additions and 1055 deletions

View File

@@ -1,22 +0,0 @@
import { Account, AccountType } from '@/models';
export default class AccountsService {
static async isAccountExists(accountId) {
const foundAccounts = await Account.tenant().query().where('id', accountId);
return foundAccounts.length > 0;
}
static async getAccountByType(accountTypeKey) {
const accountType = await AccountType.tenant()
.query()
.where('key', accountTypeKey)
.first();
const account = await Account.tenant()
.query()
.where('account_type_id', accountType.id)
.first();
return account;
}
}

View File

@@ -0,0 +1,29 @@
import { Inject, Service } from 'typedi';
import TenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class AccountsService {
@Inject()
tenancy: TenancyService;
async isAccountExists(tenantId: number, accountId: number) {
const { Account } = this.tenancy.models(tenantId);
const foundAccounts = await Account.query()
.where('id', accountId);
return foundAccounts.length > 0;
}
async getAccountByType(tenantId: number, accountTypeKey: string) {
const { AccountType, Account } = this.tenancy.models(tenantId);
const accountType = await AccountType.query()
.where('key', accountTypeKey)
.first();
const account = await Account.query()
.where('account_type_id', accountType.id)
.first();
return account;
}
}

View File

@@ -4,7 +4,7 @@ import Customer from "../../models/Customer";
export default class CustomersService {
static async isCustomerExists(customerId) {
const foundCustomeres = await Customer.tenant().query().where('id', customerId);
const foundCustomeres = await Customer.query().where('id', customerId);
return foundCustomeres.length > 0;
}
}

View File

@@ -1,23 +1,26 @@
import { Container } from 'typedi';
import {
InventoryTransaction,
Item,
Option,
} from '@/models';
import { Container, Service, Inject } from 'typedi';
import InventoryAverageCost from '@/services/Inventory/InventoryAverageCost';
import InventoryCostLotTracker from '@/services/Inventory/InventoryCostLotTracker';
import TenancyService from '@/services/Tenancy/TenancyService';
type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
@Service()
export default class InventoryService {
@Inject()
tenancy: TenancyService;
/**
* Computes the given item cost and records the inventory lots transactions
* and journal entries based on the cost method FIFO, LIFO or average cost rate.
* @param {number} tenantId -
* @param {Date} fromDate -
* @param {number} itemId -
*/
static async computeItemCost(fromDate: Date, itemId: number) {
const item = await Item.tenant().query()
async computeItemCost(tenantId: number, fromDate: Date, itemId: number) {
const { Item } = this.tenancy.models(tenantId);
const item = await Item.query()
.findById(itemId)
.withGraphFetched('category');
@@ -42,30 +45,34 @@ export default class InventoryService {
}
/**
* SChedule item cost compute job.
* Schedule item cost compute job.
* @param {number} tenantId
* @param {number} itemId
* @param {Date} startingDate
*/
static async scheduleComputeItemCost(itemId: number, startingDate: Date|string) {
async scheduleComputeItemCost(tenantId: number, itemId: number, startingDate: Date|string) {
const agenda = Container.get('agenda');
return agenda.schedule('in 3 seconds', 'compute-item-cost', {
startingDate, itemId,
startingDate, itemId, tenantId,
});
}
/**
* Records the inventory transactions.
* @param {number} tenantId - Tenant id.
* @param {Bill} bill
* @param {number} billId
*/
static async recordInventoryTransactions(
async recordInventoryTransactions(
tenantId: number,
entries: [],
deleteOld: boolean,
) {
const { InventoryTransaction, Item } = this.tenancy.models(tenantId);
const entriesItemsIds = entries.map((e: any) => e.item_id);
const inventoryItems = await Item.tenant()
.query()
const inventoryItems = await Item.query()
.whereIn('id', entriesItemsIds)
.where('type', 'inventory');
@@ -78,11 +85,12 @@ export default class InventoryService {
inventoryEntries.forEach(async (entry: any) => {
if (deleteOld) {
await this.deleteInventoryTransactions(
tenantId,
entry.transactionId,
entry.transactionType,
);
}
await InventoryTransaction.tenant().query().insert({
await InventoryTransaction.query().insert({
...entry,
lotNumber: entry.lotNumber,
});
@@ -91,15 +99,19 @@ export default class InventoryService {
/**
* Deletes the given inventory transactions.
* @param {number} tenantId - Tenant id.
* @param {string} transactionType
* @param {number} transactionId
* @return {Promise}
*/
static deleteInventoryTransactions(
deleteInventoryTransactions(
tenantId: number,
transactionId: number,
transactionType: string,
) {
return InventoryTransaction.tenant().query()
const { InventoryTransaction } = this.tenancy.models(tenantId);
return InventoryTransaction.query()
.where('transaction_type', transactionType)
.where('transaction_id', transactionId)
.delete();
@@ -107,21 +119,24 @@ export default class InventoryService {
/**
* Retrieve the lot number after the increment.
* @param {number} tenantId - Tenant id.
*/
static async nextLotNumber() {
async nextLotNumber(tenantId: number) {
const { Option } = this.tenancy.models(tenantId);
const LOT_NUMBER_KEY = 'lot_number_increment';
const effectRows = await Option.tenant().query()
const effectRows = await Option.query()
.where('key', LOT_NUMBER_KEY)
.increment('value', 1);
if (effectRows === 0) {
await Option.tenant().query()
await Option.query()
.insert({
key: LOT_NUMBER_KEY,
value: 1,
});
}
const options = await Option.tenant().query();
const options = await Option.query();
return options.getMeta(LOT_NUMBER_KEY, 1);
}
}

View File

@@ -1,5 +1,4 @@
import { pick } from 'lodash';
import { InventoryTransaction } from '@/models';
import { IInventoryTransaction } from '@/interfaces';
import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod';
@@ -10,10 +9,12 @@ export default class InventoryAverageCostMethod extends InventoryCostMethod impl
/**
* Constructor method.
* @param {number} tenantId - The given tenant id.
* @param {Date} startingDate -
* @param {number} itemId -
* @param {number} itemId - The given inventory item id.
*/
constructor(
tenantId: number,
startingDate: Date,
itemId: number,
) {
@@ -39,11 +40,10 @@ export default class InventoryAverageCostMethod extends InventoryCostMethod impl
* @param {string} referenceType
*/
public async computeItemCost() {
const { InventoryTransaction } = this.tenantModels;
const openingAvgCost = await this.getOpeningAvaregeCost(this.startingDate, this.itemId);
const afterInvTransactions: IInventoryTransaction[] = await InventoryTransaction
.tenant()
.query()
const afterInvTransactions: IInventoryTransaction[] = await InventoryTransaction.query()
.modify('filterDateRange', this.startingDate)
.orderBy('date', 'ASC')
.orderByRaw("FIELD(direction, 'IN', 'OUT')")
@@ -66,6 +66,7 @@ export default class InventoryAverageCostMethod extends InventoryCostMethod impl
* @return {number}
*/
public async getOpeningAvaregeCost(startingDate: Date, itemId: number) {
const { InventoryTransaction } = this.tenantModels;
const commonBuilder = (builder: any) => {
if (startingDate) {
builder.where('date', '<', startingDate);
@@ -81,16 +82,14 @@ export default class InventoryAverageCostMethod extends InventoryCostMethod impl
// Calculates the total inventory total quantity and rate `IN` transactions.
// @todo total `IN` transactions.
const inInvSumationOper: Promise<any> = InventoryTransaction.tenant()
.query()
const inInvSumationOper: Promise<any> = InventoryTransaction.query()
.onBuild(commonBuilder)
.where('direction', 'IN')
.first();
// Calculates the total inventory total quantity and rate `OUT` transactions.
// @todo total `OUT` transactions.
const outInvSumationOper: Promise<any> = InventoryTransaction.tenant()
.query()
const outInvSumationOper: Promise<any> = InventoryTransaction.query()
.onBuild(commonBuilder)
.where('direction', 'OUT')
.first();

View File

@@ -1,10 +1,5 @@
import { omit, pick, chain } from 'lodash';
import { pick, chain } from 'lodash';
import moment from 'moment';
import {
InventoryTransaction,
InventoryLotCostTracker,
Item,
} from "@/models";
import { IInventoryLotCost, IInventoryTransaction } from "@/interfaces";
import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod';
@@ -28,7 +23,12 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
* @param {number} itemId -
* @param {string} costMethod -
*/
constructor(startingDate: Date, itemId: number, costMethod: TCostMethod = 'FIFO') {
constructor(
tenantId: number,
startingDate: Date,
itemId: number,
costMethod: TCostMethod = 'FIFO'
) {
super();
this.startingDate = startingDate;
@@ -83,13 +83,14 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
* @private
*/
private async fetchInvINTransactions() {
const { InventoryTransaction, InventoryLotCostTracker } = this.tenantModels;
const commonBuilder = (builder: any) => {
builder.orderBy('date', (this.costMethod === 'LIFO') ? 'DESC': 'ASC');
builder.where('item_id', this.itemId);
};
const afterInvTransactions: IInventoryTransaction[] =
await InventoryTransaction.tenant()
.query()
await InventoryTransaction.query()
.modify('filterDateRange', this.startingDate)
.orderByRaw("FIELD(direction, 'IN', 'OUT')")
.onBuild(commonBuilder)
@@ -97,8 +98,7 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
.withGraphFetched('item');
const availiableINLots: IInventoryLotCost[] =
await InventoryLotCostTracker.tenant()
.query()
await InventoryLotCostTracker.query()
.modify('filterDateRange', null, this.startingDate)
.orderBy('date', 'ASC')
.where('direction', 'IN')
@@ -117,9 +117,10 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
* @private
*/
private async fetchInvOUTTransactions() {
const { InventoryTransaction } = this.tenantModels;
const afterOUTTransactions: IInventoryTransaction[] =
await InventoryTransaction.tenant()
.query()
await InventoryTransaction.query()
.modify('filterDateRange', this.startingDate)
.orderBy('date', 'ASC')
.orderBy('lot_number', 'ASC')
@@ -132,8 +133,8 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
private async fetchItemsMapped() {
const itemsIds = chain(this.inTransactions).map((e) => e.itemId).uniq().value();
const storedItems = await Item.tenant()
.query()
const { Item } = this.tenantModels;
const storedItems = await Item.query()
.where('type', 'inventory')
.whereIn('id', itemsIds);
@@ -145,9 +146,9 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
* @private
*/
private async fetchRevertInvJReferenceIds() {
const { InventoryTransaction } = this.tenantModels;
const revertJEntriesTransactions: IInventoryTransaction[] =
await InventoryTransaction.tenant()
.query()
await InventoryTransaction.query()
.select(['transactionId', 'transactionType'])
.modify('filterDateRange', this.startingDate)
.where('direction', 'OUT')
@@ -164,16 +165,15 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
* @return {Promise}
*/
public async revertInventoryLots(startingDate: Date) {
const { InventoryLotCostTracker } = this.tenantModels;
const asyncOpers: any[] = [];
const inventoryLotsTrans = await InventoryLotCostTracker.tenant()
.query()
const inventoryLotsTrans = await InventoryLotCostTracker.query()
.modify('filterDateRange', this.startingDate)
.orderBy('date', 'DESC')
.where('item_id', this.itemId)
.where('direction', 'OUT');
const deleteInvLotsTrans = InventoryLotCostTracker.tenant()
.query()
const deleteInvLotsTrans = InventoryLotCostTracker.query()
.modify('filterDateRange', this.startingDate)
.where('item_id', this.itemId)
.delete();
@@ -181,8 +181,7 @@ export default class InventoryCostLotTracker extends InventoryCostMethod impleme
inventoryLotsTrans.forEach((inventoryLot: IInventoryLotCost) => {
if (!inventoryLot.lotNumber) { return; }
const incrementOper = InventoryLotCostTracker.tenant()
.query()
const incrementOper = InventoryLotCostTracker.query()
.where('lot_number', inventoryLot.lotNumber)
.where('direction', 'IN')
.increment('remaining', inventoryLot.quantity);

View File

@@ -1,28 +1,42 @@
import { omit } from 'lodash';
import { Inject } from 'typedi';
import TenancyService from '@/services/Tenancy/TenancyService';
import { IInventoryLotCost } from '@/interfaces';
import { InventoryLotCostTracker } from '@/models';
export default class InventoryCostMethod {
@Inject()
tenancy: TenancyService;
tenantModels: any;
/**
* Constructor method.
* @param {number} tenantId - The given tenant id.
*/
constructor(tenantId: number, startingDate: Date, itemId: number) {
this.tenantModels = this.tenantModels.models(tenantId);
}
/**
* Stores the inventory lots costs transactions in bulk.
* @param {IInventoryLotCost[]} costLotsTransactions
* @return {Promise[]}
*/
public storeInventoryLotsCost(costLotsTransactions: IInventoryLotCost[]): Promise<object> {
const { InventoryLotCostTracker } = this.tenantModels;
const opers: any = [];
costLotsTransactions.forEach((transaction: IInventoryLotCost) => {
if (transaction.lotTransId && transaction.decrement) {
const decrementOper = InventoryLotCostTracker.tenant()
.query()
const decrementOper = InventoryLotCostTracker.query()
.where('id', transaction.lotTransId)
.decrement('remaining', transaction.decrement);
opers.push(decrementOper);
} else if(!transaction.lotTransId) {
const operation = InventoryLotCostTracker.tenant().query()
.insert({
...omit(transaction, ['decrement', 'invTransId', 'lotTransId']),
});
const operation = InventoryLotCostTracker.query()
.insert({
...omit(transaction, ['decrement', 'invTransId', 'lotTransId']),
});
opers.push(operation);
}
});

View File

@@ -1,59 +0,0 @@
import { difference } from "lodash";
import { Item, ItemTransaction } from '@/models';
export default class ItemsService {
static async newItem(item) {
const storedItem = await Item.tenant()
.query()
.insertAndFetch({
...item,
});
return storedItem;
}
static async editItem(item, itemId) {
const updateItem = await Item.tenant()
.query()
.findById(itemId)
.patch({
...item,
});
return updateItem;
}
static async deleteItem(itemId) {
return Item.tenant()
.query()
.findById(itemId)
.delete();
}
static async getItemWithMetadata(itemId) {
return Item.tenant()
.query()
.findById(itemId)
.withGraphFetched(
'costAccount',
'sellAccount',
'inventoryAccount',
'category'
);
}
/**
* Validates the given items IDs exists or not returns the not found ones.
* @param {Array} itemsIDs
* @return {Array}
*/
static async isItemsIdsExists(itemsIDs) {
const storedItems = await Item.tenant().query().whereIn('id', itemsIDs);
const storedItemsIds = storedItems.map((t) => t.id);
const notFoundItemsIds = difference(
itemsIDs,
storedItemsIds,
);
return notFoundItemsIds;
}
}

View File

@@ -0,0 +1,70 @@
import { difference } from "lodash";
import { Service, Inject } from "typedi";
import TenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class ItemsService {
@Inject()
tenancy: TenancyService;
async newItem(tenantId: number, item: any) {
const { Item } = this.tenancy.models(tenantId);
const storedItem = await Item.query()
.insertAndFetch({
...item,
});
return storedItem;
}
async editItem(tenantId: number, item: any, itemId: number) {
const { Item } = this.tenancy.models(tenantId);
const updateItem = await Item.query()
.findById(itemId)
.patch({
...item,
});
return updateItem;
}
async deleteItem(tenantId: number, itemId: number) {
const { Item } = this.tenancy.models(tenantId);
return Item.query()
.findById(itemId)
.delete();
}
/**
* Retrieve the item details of the given id with associated details.
* @param {number} tenantId
* @param {number} itemId
*/
async getItemWithMetadata(tenantId: number, itemId: number) {
const { Item } = this.tenancy.models(tenantId);
return Item.query()
.findById(itemId)
.withGraphFetched(
'costAccount',
'sellAccount',
'inventoryAccount',
'category'
);
}
/**
* Validates the given items IDs exists or not returns the not found ones.
* @param {Array} itemsIDs
* @return {Array}
*/
async isItemsIdsExists(tenantId: number, itemsIDs: number[]) {
const { Item } = this.tenancy.models(tenantId);
const storedItems = await Item.query().whereIn('id', itemsIDs);
const storedItemsIds = storedItems.map((t) => t.id);
const notFoundItemsIds = difference(
itemsIDs,
storedItemsIds,
);
return notFoundItemsIds;
}
}

View File

@@ -1,5 +1,6 @@
import { Service, Container, Inject } from 'typedi';
import cryptoRandomString from 'crypto-random-string';
import { times } from 'lodash';
import { Voucher } from "@/system/models";
import { IVoucher } from '@/interfaces';
import VoucherMailMessages from '@/services/Payment/VoucherMailMessages';
@@ -26,6 +27,8 @@ export default class VoucherService {
let voucherCode: string;
let repeat: boolean = true;
console.log(Voucher);
while(repeat) {
voucherCode = cryptoRandomString({ length: 10, type: 'numeric' });
const foundVouchers = await Voucher.query().where('voucher_code', voucherCode);
@@ -39,6 +42,29 @@ export default class VoucherService {
});
}
/**
*
* @param {number} loop
* @param {number} voucherPeriod
* @param {string} periodInterval
* @param {number} planId
*/
async generateVouchers(
loop = 1,
voucherPeriod: numner,
periodInterval: string = 'days',
planId: number,
) {
const asyncOpers: Promise<any>[] = [];
times(loop, () => {
const generateOper = this.generateVoucher(voucherPeriod, periodInterval, planId);
asyncOpers.push(generateOper);
});
return Promise.all(asyncOpers);
}
/**
* Disables the given voucher id on the storage.
* @param {number} voucherId

View File

@@ -1,26 +1,30 @@
import express from 'express';
import { Inject, Service } from 'typedi';
import { omit, sumBy } from 'lodash';
import moment from 'moment';
import {
BillPayment,
BillPaymentEntry,
Vendor,
Bill,
Account,
AccountTransaction,
} from '@/models';
import { IBillPaymentOTD, IBillPayment } from '@/interfaces';
import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries';
import AccountsService from '@/services/Accounts/AccountsService';
import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry';
import JournalPosterService from '@/services/Sales/JournalPosterService';
import TenancyService from '@/services/Tenancy/TenancyService';
import { formatDateFields } from '@/utils';
/**
* Bill payments service.
* @service
*/
@Service()
export default class BillPaymentsService {
@Inject()
accountsService: AccountsService;
@Inject()
tenancy: TenancyService;
@Inject()
journalService: JournalPosterService;
/**
* Creates a new bill payment transcations and store it to the storage
* with associated bills entries and journal transactions.
@@ -32,24 +36,24 @@ export default class BillPaymentsService {
* - Increment the payment amount of the given vendor bills.
* - Decrement the vendor balance.
* - Records payment journal entries.
*
* @param {BillPaymentDTO} billPayment
* @param {number} tenantId - Tenant id.
* @param {BillPaymentDTO} billPayment - Bill payment object.
*/
static async createBillPayment(billPaymentDTO) {
async createBillPayment(tenantId: number, billPaymentDTO: IBillPaymentOTD) {
const { Bill, BillPayment, BillPaymentEntry, Vendor } = this.tenancy.models(tenantId);
const billPayment = {
amount: sumBy(billPaymentDTO.entries, 'payment_amount'),
...formatDateFields(billPaymentDTO, ['payment_date']),
}
const storedBillPayment = await BillPayment.tenant()
.query()
const storedBillPayment = await BillPayment.query()
.insert({
...omit(billPayment, ['entries']),
});
const storeOpers = [];
const storeOpers: Promise<any>[] = [];
billPayment.entries.forEach((entry) => {
const oper = BillPaymentEntry.tenant()
.query()
const oper = BillPaymentEntry.query()
.insert({
bill_payment_id: storedBillPayment.id,
...entry,
@@ -69,7 +73,7 @@ export default class BillPaymentsService {
);
// Records the journal transactions after bills payment
// and change diff acoount balance.
const recordJournalTransaction = this.recordPaymentReceiveJournalEntries({
const recordJournalTransaction = this.recordPaymentReceiveJournalEntries(tenantId, {
id: storedBillPayment.id,
...billPayment,
});
@@ -93,18 +97,23 @@ export default class BillPaymentsService {
* - Re-insert the journal transactions and update the diff accounts balance.
* - Update the diff vendor balance.
* - Update the diff bill payment amount.
*
* @param {number} tenantId - Tenant id
* @param {Integer} billPaymentId
* @param {BillPaymentDTO} billPayment
* @param {IBillPayment} oldBillPayment
*/
static async editBillPayment(billPaymentId, billPaymentDTO, oldBillPayment) {
async editBillPayment(
tenantId: number,
billPaymentId: number,
billPaymentDTO,
oldBillPayment,
) {
const { BillPayment, BillPaymentEntry, Vendor } = this.tenancy.models(tenantId);
const billPayment = {
amount: sumBy(billPaymentDTO.entries, 'payment_amount'),
...formatDateFields(billPaymentDTO, ['payment_date']),
};
const updateBillPayment = await BillPayment.tenant()
.query()
const updateBillPayment = await BillPayment.query()
.where('id', billPaymentId)
.update({
...omit(billPayment, ['entries']),
@@ -118,22 +127,19 @@ export default class BillPaymentsService {
entriesHasIds
);
if (entriesIdsShouldDelete.length > 0) {
const deleteOper = BillPaymentEntry.tenant()
.query()
const deleteOper = BillPaymentEntry.query()
.bulkDelete(entriesIdsShouldDelete);
opers.push(deleteOper);
}
// Entries that should be update to the storage.
if (entriesHasIds.length > 0) {
const updateOper = BillPaymentEntry.tenant()
.query()
const updateOper = BillPaymentEntry.query()
.bulkUpdate(entriesHasIds, { where: 'id' });
opers.push(updateOper);
}
// Entries that should be inserted to the storage.
if (entriesHasNoIds.length > 0) {
const insertOper = BillPaymentEntry.tenant()
.query()
const insertOper = BillPaymentEntry.query()
.bulkInsert(
entriesHasNoIds.map((e) => ({ ...e, bill_payment_id: billPaymentId }))
);
@@ -141,7 +147,7 @@ export default class BillPaymentsService {
}
// Records the journal transactions after bills payment and change
// different acoount balance.
const recordJournalTransaction = this.recordPaymentReceiveJournalEntries({
const recordJournalTransaction = this.recordPaymentReceiveJournalEntries(tenantId, {
id: storedBillPayment.id,
...billPayment,
});
@@ -161,18 +167,19 @@ export default class BillPaymentsService {
/**
* Deletes the bill payment and associated transactions.
* @param {number} tenantId - Tenant id.
* @param {Integer} billPaymentId - The given bill payment id.
* @return {Promise}
*/
static async deleteBillPayment(billPaymentId) {
const billPayment = await BillPayment.tenant().query().where('id', billPaymentId).first();
async deleteBillPayment(tenantId: number, billPaymentId: number) {
const { BillPayment, BillPaymentEntry, Vendor } = this.tenancy.models(tenantId);
const billPayment = await BillPayment.query().where('id', billPaymentId).first();
await BillPayment.tenant().query()
await BillPayment.query()
.where('id', billPaymentId)
.delete();
await BillPaymentEntry.tenant()
.query()
await BillPaymentEntry.query()
.where('bill_payment_id', billPaymentId)
.delete();
@@ -192,17 +199,21 @@ export default class BillPaymentsService {
/**
* Records bill payment receive journal transactions.
* @param {number} tenantId -
* @param {BillPayment} billPayment
* @param {Integer} billPaymentId
*/
static async recordPaymentReceiveJournalEntries(billPayment) {
async recordPaymentReceiveJournalEntries(tenantId: number, billPayment) {
const { AccountTransaction, Account } = this.tenancy.models(tenantId);
const paymentAmount = sumBy(billPayment.entries, 'payment_amount');
const formattedDate = moment(billPayment.payment_date).format('YYYY-MM-DD');
const payableAccount = await AccountsService.getAccountByType(
const payableAccount = await this.accountsService.getAccountByType(
tenantId,
'accounts_payable'
);
const accountsDepGraph = await Account.tenant().depGraph().query();
const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph);
const commonJournal = {
@@ -213,8 +224,7 @@ export default class BillPaymentsService {
date: formattedDate,
};
if (billPayment.id) {
const transactions = await AccountTransaction.tenant()
.query()
const transactions = await AccountTransaction.query()
.whereIn('reference_type', ['BillPayment'])
.where('reference_id', billPayment.id)
.withGraphFetched('account.type');
@@ -246,11 +256,12 @@ export default class BillPaymentsService {
/**
* Retrieve bill payment with associated metadata.
* @param {number} billPaymentId
* @param {number} billPaymentId - The bill payment id.
* @return {object}
*/
static async getBillPaymentWithMetadata(billPaymentId) {
const billPayment = await BillPayment.tenant().query()
async getBillPaymentWithMetadata(tenantId: number, billPaymentId: number) {
const { BillPayment } = this.tenancy.models(tenantId);
const billPayment = await BillPayment.query()
.where('id', billPaymentId)
.withGraphFetched('entries')
.withGraphFetched('vendor')
@@ -265,8 +276,9 @@ export default class BillPaymentsService {
* @param {Integer} billPaymentId
* @return {boolean}
*/
static async isBillPaymentExists(billPaymentId) {
const billPayment = await BillPayment.tenant().query()
async isBillPaymentExists(tenantId: number, billPaymentId: number) {
const { BillPayment } = this.tenancy.models(tenantId);
const billPayment = await BillPayment.query()
.where('id', billPaymentId)
.first();

View File

@@ -1,13 +1,6 @@
import { omit, sumBy, pick } from 'lodash';
import moment from 'moment';
import {
Account,
Bill,
Vendor,
ItemEntry,
Item,
AccountTransaction,
} from '@/models';
import { Inject, Service } from 'typedi';
import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry';
import AccountsService from '@/services/Accounts/AccountsService';
@@ -15,13 +8,25 @@ import JournalPosterService from '@/services/Sales/JournalPosterService';
import InventoryService from '@/services/Inventory/Inventory';
import HasItemsEntries from '@/services/Sales/HasItemsEntries';
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost';
import TenancyService from '@/services/Tenancy/TenancyService';
import { formatDateFields } from '@/utils';
import{ IBillOTD } from '@/interfaces';
/**
* Vendor bills services.
* @service
*/
@Service()
export default class BillsService extends SalesInvoicesCost {
@Inject()
inventoryService: InventoryService;
@Inject()
accountsService: AccountsService;
@Inject()
tenancy: TenancyService;
/**
* Creates a new bill and stored it to the storage.
*
@@ -33,26 +38,29 @@ export default class BillsService extends SalesInvoicesCost {
* - Record bill journal transactions on the given accounts.
* - Record bill items inventory transactions.
* ----
* @param {IBill} bill -
* @param {number} tenantId - The given tenant id.
* @param {IBillOTD} billDTO -
* @return {void}
*/
static async createBill(billDTO) {
const invLotNumber = await InventoryService.nextLotNumber();
async createBill(tenantId: number, billDTO: IBillOTD) {
const { Vendor, Bill, ItemEntry } = this.tenancy.models(tenantId);
const invLotNumber = await this.inventoryService.nextLotNumber(tenantId);
const amount = sumBy(billDTO.entries, e => ItemEntry.calcAmount(e));
const bill = {
...formatDateFields(billDTO, ['bill_date', 'due_date']),
amount: sumBy(billDTO.entries, 'amount'),
amount,
invLotNumber: billDTO.invLotNumber || invLotNumber
};
const saveEntriesOpers = [];
const storedBill = await Bill.tenant()
.query()
const storedBill = await Bill.query()
.insert({
...omit(bill, ['entries']),
});
bill.entries.forEach((entry) => {
const oper = ItemEntry.tenant()
.query()
const oper = ItemEntry.query()
.insertAndFetch({
reference_type: 'Bill',
reference_id: storedBill.id,
@@ -70,10 +78,10 @@ export default class BillsService extends SalesInvoicesCost {
// Rewrite the inventory transactions for inventory items.
const writeInvTransactionsOper = this.recordInventoryTransactions(
bill, storedBill.id
tenantId, bill, storedBill.id
);
// Writes the journal entries for the given bill transaction.
const writeJEntriesOper = this.recordJournalTransactions({
const writeJEntriesOper = this.recordJournalTransactions(tenantId, {
id: storedBill.id, ...bill,
});
await Promise.all([
@@ -83,7 +91,7 @@ export default class BillsService extends SalesInvoicesCost {
]);
// Schedule bill re-compute based on the item cost
// method and starting date.
await this.scheduleComputeBillItemsCost(bill);
await this.scheduleComputeBillItemsCost(tenantId, bill);
return storedBill;
}
@@ -100,26 +108,29 @@ export default class BillsService extends SalesInvoicesCost {
* - 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 {IBill} bill - The given new bill details.
* @param {billDTO} billDTO - The given new bill details.
*/
static async editBill(billId, billDTO) {
const oldBill = await Bill.tenant().query().findById(billId);
async editBill(tenantId: number, billId: number, billDTO: billDTO) {
const { Bill, ItemEntry, Vendor } = this.tenancy.models(tenantId);
const oldBill = await Bill.query().findById(billId);
const amount = sumBy(billDTO.entries, e => ItemEntry.calcAmount(e));
const bill = {
...formatDateFields(billDTO, ['bill_date', 'due_date']),
amount: sumBy(billDTO.entries, 'amount'),
amount,
invLotNumber: oldBill.invLotNumber,
};
// Update the bill transaction.
const updatedBill = await Bill.tenant()
.query()
const updatedBill = await Bill.query()
.where('id', billId)
.update({
...omit(bill, ['entries', 'invLotNumber'])
});
// Old stored entries.
const storedEntries = await ItemEntry.tenant()
.query()
const storedEntries = await ItemEntry.query()
.where('reference_id', billId)
.where('reference_type', 'Bill');
@@ -135,10 +146,11 @@ export default class BillsService extends SalesInvoicesCost {
oldBill.amount,
);
// Re-write the inventory transactions for inventory items.
const writeInvTransactionsOper = this.recordInventoryTransactions(bill, billId, true);
const writeInvTransactionsOper = this.recordInventoryTransactions(
tenantId, bill, billId, true
);
// Writes the journal entries for the given bill transaction.
const writeJEntriesOper = this.recordJournalTransactions({
const writeJEntriesOper = this.recordJournalTransactions(tenantId, {
id: billId,
...bill,
}, billId);
@@ -151,7 +163,7 @@ export default class BillsService extends SalesInvoicesCost {
]);
// Schedule sale invoice re-compute based on the item cost
// method and starting date.
await this.scheduleComputeBillItemsCost(bill);
await this.scheduleComputeBillItemsCost(tenantId, bill);
}
/**
@@ -159,21 +171,22 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Integer} billId
* @return {void}
*/
static async deleteBill(billId) {
const bill = await Bill.tenant().query()
async deleteBill(tenantId: number, billId: number) {
const { Bill, ItemEntry, Vendor } = this.tenancy.models(tenantId);
const bill = await Bill.query()
.where('id', billId)
.withGraphFetched('entries')
.first();
// Delete all associated bill entries.
const deleteBillEntriesOper = ItemEntry.tenant()
.query()
const deleteBillEntriesOper = ItemEntry.query()
.where('reference_type', 'Bill')
.where('reference_id', billId)
.delete();
// Delete the bill transaction.
const deleteBillOper = Bill.tenant().query().where('id', billId).delete();
const deleteBillOper = Bill.query().where('id', billId).delete();
// Delete associated bill journal transactions.
const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions(
@@ -181,8 +194,8 @@ export default class BillsService extends SalesInvoicesCost {
'Bill'
);
// Delete bill associated inventory transactions.
const deleteInventoryTransOper = InventoryService.deleteInventoryTransactions(
billId, 'Bill'
const deleteInventoryTransOper = this.inventoryService.deleteInventoryTransactions(
tenantId, billId, 'Bill'
);
// Revert vendor balance.
const revertVendorBalance = Vendor.changeBalance(bill.vendorId, bill.amount * -1);
@@ -196,7 +209,7 @@ export default class BillsService extends SalesInvoicesCost {
]);
// Schedule sale invoice re-compute based on the item cost
// method and starting date.
await this.scheduleComputeBillItemsCost(bill);
await this.scheduleComputeBillItemsCost(tenantId, bill);
}
/**
@@ -204,7 +217,12 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Bill} bill
* @param {number} billId
*/
static recordInventoryTransactions(bill, billId, override) {
recordInventoryTransactions(
tenantId: number,
bill: any,
billId: number,
override?: boolean
) {
const inventoryTransactions = bill.entries
.map((entry) => ({
...pick(entry, ['item_id', 'quantity', 'rate']),
@@ -216,8 +234,8 @@ export default class BillsService extends SalesInvoicesCost {
entryId: entry.id,
}));
return InventoryService.recordInventoryTransactions(
inventoryTransactions, override
return this.inventoryService.recordInventoryTransactions(
tenantId, inventoryTransactions, override
);
}
@@ -227,19 +245,21 @@ export default class BillsService extends SalesInvoicesCost {
* @param {IBill} bill
* @param {Integer} billId
*/
static async recordJournalTransactions(bill, billId) {
async recordJournalTransactions(tenantId: number, bill: any, billId?: number) {
const { AccountTransaction, Item, Account } = this.tenancy.models(tenantId);
const entriesItemsIds = bill.entries.map((entry) => entry.item_id);
const payableTotal = sumBy(bill.entries, 'amount');
const formattedDate = moment(bill.bill_date).format('YYYY-MM-DD');
const storedItems = await Item.tenant()
.query()
const storedItems = await Item.query()
.whereIn('id', entriesItemsIds);
const storedItemsMap = new Map(storedItems.map((item) => [item.id, item]));
const payableAccount = await AccountsService.getAccountByType('accounts_payable');
const accountsDepGraph = await Account.tenant().depGraph().query();
const payableAccount = await this.accountsService.getAccountByType(
tenantId, 'accounts_payable'
);
const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph);
const commonJournalMeta = {
@@ -251,8 +271,7 @@ export default class BillsService extends SalesInvoicesCost {
accural: true,
};
if (billId) {
const transactions = await AccountTransaction.tenant()
.query()
const transactions = await AccountTransaction.query()
.whereIn('reference_type', ['Bill'])
.whereIn('reference_id', [billId])
.withGraphFetched('account.type');
@@ -291,11 +310,14 @@ export default class BillsService extends SalesInvoicesCost {
/**
* Detarmines whether the bill exists on the storage.
* @param {Integer} billId
* @param {number} tenantId - The given tenant id.
* @param {Integer} billId - The given bill id.
* @return {Boolean}
*/
static async isBillExists(billId) {
const foundBills = await Bill.tenant().query().where('id', billId);
async isBillExists(tenantId: number, billId: number) {
const { Bill } = this.tenancy.models(tenantId);
const foundBills = await Bill.query().where('id', billId);
return foundBills.length > 0;
}
@@ -304,18 +326,23 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Array} billsIds
* @return {Boolean}
*/
static async isBillsExist(billsIds) {
const bills = await Bill.tenant().query().whereIn('id', billsIds);
async isBillsExist(tenantId: number, billsIds: number[]) {
const { Bill } = this.tenancy.models(tenantId);
const bills = await Bill.query().whereIn('id', billsIds);
return bills.length > 0;
}
/**
* Detarmines whether the given bill id exists on the storage.
* @param {number} tenantId
* @param {Integer} billNumber
* @return {boolean}
*/
static async isBillNoExists(billNumber) {
const foundBills = await Bill.tenant()
.query()
async isBillNoExists(tenantId: number, billNumber : string) {
const { Bill } = this.tenancy.models(tenantId);
const foundBills = await Bill.query()
.where('bill_number', billNumber);
return foundBills.length > 0;
}
@@ -325,8 +352,10 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Integer} billId -
* @returns {Promise}
*/
static getBill(billId) {
return Bill.tenant().query().where('id', billId).first();
getBill(tenantId: number, billId: number) {
const { Bill } = this.tenancy.models(tenantId);
return Bill.query().where('id', billId).first();
}
/**
@@ -334,9 +363,10 @@ export default class BillsService extends SalesInvoicesCost {
* @param {Integer} billId -
* @returns {Promise}
*/
static getBillWithMetadata(billId) {
return Bill.tenant()
.query()
getBillWithMetadata(tenantId: number, billId: number) {
const { Bill } = this.tenancy.models(tenantId);
return Bill.query()
.where('id', billId)
.withGraphFetched('vendor')
.withGraphFetched('entries')
@@ -345,21 +375,27 @@ export default class BillsService extends SalesInvoicesCost {
/**
* Schedules compute bill items cost based on each item cost method.
* @param {IBill} bill
* @param {number} tenantId -
* @param {IBill} bill -
* @return {Promise}
*/
static async scheduleComputeBillItemsCost(bill) {
async scheduleComputeBillItemsCost(tenantId: number, bill) {
const { Item } = this.tenancy.models(tenantId);
const billItemsIds = bill.entries.map((entry) => entry.item_id);
// Retrieves inventory items only.
const inventoryItems = await Item.tenant().query()
const inventoryItems = await Item.query()
.whereIn('id', billItemsIds)
.where('type', 'inventory');
const inventoryItemsIds = inventoryItems.map(i => i.id);
if (inventoryItemsIds.length > 0) {
await this.scheduleComputeItemsCost(inventoryItemsIds, bill.bill_date);
await this.scheduleComputeItemsCost(
tenantId,
inventoryItemsIds,
bill.bill_date
);
}
}
}
}

View File

@@ -1,7 +1,13 @@
import { difference, omit } from 'lodash';
import { Service, Inject } from 'typedi';
import TenancyService from '@/services/Tenancy/TenancyService';
import { ItemEntry } from '@/models';
@Service()
export default class HasItemEntries {
@Inject()
tenancy: TenancyService;
/**
* Patch items entries to the storage.
*
@@ -9,15 +15,17 @@ export default class HasItemEntries {
* @param {Array} oldEntries -
* @param {String} referenceType -
* @param {String|Number} referenceId -
*
* @return {Promise}
*/
static async patchItemsEntries(
async patchItemsEntries(
tenantId: number,
newEntries: Array<any>,
oldEntries: Array<any>,
referenceType: string,
referenceId: string|number
) {
const { ItemEntry } = this.tenancy.models(tenantId);
const entriesHasIds = newEntries.filter((entry) => entry.id);
const entriesHasNoIds = newEntries.filter((entry) => !entry.id);
const entriesIds = entriesHasIds.map(entry => entry.id);
@@ -31,15 +39,13 @@ export default class HasItemEntries {
entriesIds,
);
if (entriesIdsShouldDelete.length > 0) {
const deleteOper = ItemEntry.tenant()
.query()
const deleteOper = ItemEntry.query()
.whereIn('id', entriesIdsShouldDelete)
.delete();
opers.push(deleteOper);
}
entriesHasIds.forEach((entry) => {
const updateOper = ItemEntry.tenant()
.query()
const updateOper = ItemEntry.query()
.where('id', entry.id)
.update({
...omit(entry, excludeAttrs),
@@ -47,8 +53,7 @@ export default class HasItemEntries {
opers.push(updateOper);
});
entriesHasNoIds.forEach((entry) => {
const insertOper = ItemEntry.tenant()
.query()
const insertOper = ItemEntry.query()
.insert({
reference_id: referenceId,
reference_type: referenceType,
@@ -59,7 +64,7 @@ export default class HasItemEntries {
return Promise.all([...opers]);
}
static filterNonInventoryEntries(entries: [], items: []) {
filterNonInventoryEntries(entries: [], items: []) {
const nonInventoryItems = items.filter((item: any) => item.type !== 'inventory');
const nonInventoryItemsIds = nonInventoryItems.map((i: any) => i.id);
@@ -69,7 +74,7 @@ export default class HasItemEntries {
));
}
static filterInventoryEntries(entries: [], items: []) {
filterInventoryEntries(entries: [], items: []) {
const inventoryItems = items.filter((item: any) => item.type === 'inventory');
const inventoryItemsIds = inventoryItems.map((i: any) => i.id);

View File

@@ -1,12 +1,26 @@
import { Account, AccountTransaction } from '@/models';
import { Service, Inject } from 'typedi';
import JournalPoster from '@/services/Accounting/JournalPoster';
import TenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class JournalPosterService {
@Inject()
tenancy: TenancyService;
/**
* Deletes the journal transactions that associated to the given reference id.
* @param {number} tenantId - The given tenant id.
* @param {number} referenceId - The transaction reference id.
* @param {string} referenceType - The transaction reference type.
* @return {Promise}
*/
static async deleteJournalTransactions(referenceId, referenceType) {
async deleteJournalTransactions(
tenantId: number,
referenceId: number,
referenceType: string
) {
const { Account, AccountTransaction } = this.tenancy.models(tenantId);
const transactions = await AccountTransaction.tenant()
.query()
.whereIn('reference_type', [referenceType])

View File

@@ -1,13 +1,7 @@
import { omit, sumBy, chain } from 'lodash';
import moment from 'moment';
import {
AccountTransaction,
PaymentReceive,
PaymentReceiveEntry,
SaleInvoice,
Customer,
Account,
} from '@/models';
import { Service, Inject } from 'typedi';
import { IPaymentReceiveOTD } from '@/interfaces';
import AccountsService from '@/services/Accounts/AccountsService';
import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry';
@@ -15,23 +9,41 @@ import JournalPosterService from '@/services/Sales/JournalPosterService';
import ServiceItemsEntries from '@/services/Sales/ServiceItemsEntries';
import PaymentReceiveEntryRepository from '@/repositories/PaymentReceiveEntryRepository';
import CustomerRepository from '@/repositories/CustomerRepository';
import TenancyService from '@/services/Tenancy/TenancyService';
import { formatDateFields } from '@/utils';
/**
* Payment receive service.
* @service
*/
@Service()
export default class PaymentReceiveService {
@Inject()
accountsService: AccountsService;
@Inject()
tenancy: TenancyService;
@Inject()
journalService: JournalPosterService;
/**
* Creates a new payment receive and store it to the storage
* with associated invoices payment and journal transactions.
* @async
* @param {number} tenantId - Tenant id.
* @param {IPaymentReceive} paymentReceive
*/
static async createPaymentReceive(paymentReceive: any) {
async createPaymentReceive(tenantId: number, paymentReceive: IPaymentReceiveOTD) {
const {
PaymentReceive,
PaymentReceiveEntry,
SaleInvoice,
Customer,
} = this.tenancy.models(tenantId);
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
const storedPaymentReceive = await PaymentReceive.tenant()
.query()
const storedPaymentReceive = await PaymentReceive.query()
.insert({
amount: paymentAmount,
...formatDateFields(omit(paymentReceive, ['entries']), ['payment_date']),
@@ -39,15 +51,13 @@ export default class PaymentReceiveService {
const storeOpers: Array<any> = [];
paymentReceive.entries.forEach((entry: any) => {
const oper = PaymentReceiveEntry.tenant()
.query()
const oper = PaymentReceiveEntry.query()
.insert({
payment_receive_id: storedPaymentReceive.id,
...entry,
});
// Increment the invoice payment amount.
const invoice = SaleInvoice.tenant()
.query()
const invoice = SaleInvoice.query()
.where('id', entry.invoice_id)
.increment('payment_amount', entry.payment_amount);
@@ -59,10 +69,10 @@ export default class PaymentReceiveService {
paymentAmount,
);
// Records the sale invoice journal transactions.
const recordJournalTransactions = this.recordPaymentReceiveJournalEntries({
id: storedPaymentReceive.id,
...paymentReceive,
});
const recordJournalTransactions = this.recordPaymentReceiveJournalEntries(tenantId,{
id: storedPaymentReceive.id,
...paymentReceive,
});
await Promise.all([
...storeOpers,
customerIncrementOper,
@@ -82,19 +92,22 @@ export default class PaymentReceiveService {
* - Update the different customer balances.
* - Update the different invoice payment amount.
* @async
* @param {Integer} paymentReceiveId
* @param {IPaymentReceive} paymentReceive
* @param {IPaymentReceive} oldPaymentReceive
* @param {number} tenantId -
* @param {Integer} paymentReceiveId -
* @param {IPaymentReceive} paymentReceive -
* @param {IPaymentReceive} oldPaymentReceive -
*/
static async editPaymentReceive(
async editPaymentReceive(
tenantId: number,
paymentReceiveId: number,
paymentReceive: any,
oldPaymentReceive: any
) {
const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
// Update the payment receive transaction.
const updatePaymentReceive = await PaymentReceive.tenant()
.query()
const updatePaymentReceive = await PaymentReceive.query()
.where('id', paymentReceiveId)
.update({
amount: paymentAmount,
@@ -131,6 +144,7 @@ export default class PaymentReceiveService {
}
// Re-write the journal transactions of the given payment receive.
const recordJournalTransactions = this.recordPaymentReceiveJournalEntries(
tenantId,
{
id: oldPaymentReceive.id,
...paymentReceive,
@@ -147,6 +161,7 @@ export default class PaymentReceiveService {
);
// Change the difference between the old and new invoice payment amount.
const diffInvoicePaymentAmount = this.saveChangeInvoicePaymentAmount(
tenantId,
oldPaymentReceive.entries,
paymentReceive.entries
);
@@ -169,24 +184,26 @@ export default class PaymentReceiveService {
* - Revert the customer balance.
* - Revert the payment amount of the associated invoices.
* @async
* @param {Integer} paymentReceiveId
* @param {IPaymentReceive} paymentReceive
* @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveId - Payment receive id.
* @param {IPaymentReceive} paymentReceive - Payment receive object.
*/
static async deletePaymentReceive(paymentReceiveId: number, paymentReceive: any) {
async deletePaymentReceive(tenantId: number, paymentReceiveId: number, paymentReceive: any) {
const { PaymentReceive, PaymentReceiveEntry, Customer } = this.tenancy.models(tenantId);
// Deletes the payment receive transaction.
await PaymentReceive.tenant()
.query()
await PaymentReceive.query()
.where('id', paymentReceiveId)
.delete();
// Deletes the payment receive associated entries.
await PaymentReceiveEntry.tenant()
.query()
await PaymentReceiveEntry.query()
.where('payment_receive_id', paymentReceiveId)
.delete();
// Delete all associated journal transactions to payment receive transaction.
const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions(
const deleteTransactionsOper = this.journalService.deleteJournalTransactions(
tenantId,
paymentReceiveId,
'PaymentReceive'
);
@@ -197,6 +214,7 @@ export default class PaymentReceiveService {
);
// Revert the invoices payments amount.
const revertInvoicesPaymentAmount = this.revertInvoicePaymentAmount(
tenantId,
paymentReceive.entries.map((entry: any) => ({
invoiceId: entry.invoiceId,
revertAmount: entry.paymentAmount,
@@ -211,11 +229,12 @@ export default class PaymentReceiveService {
/**
* Retrieve the payment receive details of the given id.
* @param {Integer} paymentReceiveId
* @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveId - Payment receive id.
*/
static async getPaymentReceive(paymentReceiveId: number) {
const paymentReceive = await PaymentReceive.tenant()
.query()
async getPaymentReceive(tenantId: number, paymentReceiveId: number) {
const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentReceive = await PaymentReceive.query()
.where('id', paymentReceiveId)
.withGraphFetched('entries.invoice')
.first();
@@ -226,9 +245,9 @@ export default class PaymentReceiveService {
* Retrieve the payment receive details with associated invoices.
* @param {Integer} paymentReceiveId
*/
static async getPaymentReceiveWithInvoices(paymentReceiveId: number) {
return PaymentReceive.tenant()
.query()
async getPaymentReceiveWithInvoices(tenantId: number, paymentReceiveId: number) {
const { PaymentReceive } = this.tenancy.models(tenantId);
return PaymentReceive.query()
.where('id', paymentReceiveId)
.withGraphFetched('invoices')
.first();
@@ -236,11 +255,12 @@ export default class PaymentReceiveService {
/**
* Detarmines whether the payment receive exists on the storage.
* @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveId
*/
static async isPaymentReceiveExists(paymentReceiveId: number) {
const paymentReceives = await PaymentReceive.tenant()
.query()
async isPaymentReceiveExists(tenantId: number, paymentReceiveId: number) {
const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentReceives = await PaymentReceive.query()
.where('id', paymentReceiveId);
return paymentReceives.length > 0;
}
@@ -248,15 +268,17 @@ export default class PaymentReceiveService {
/**
* Detarmines the payment receive number existance.
* @async
* @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveNumber - Payment receive number.
* @param {Integer} paymentReceiveId - Payment receive id.
*/
static async isPaymentReceiveNoExists(
async isPaymentReceiveNoExists(
tenantId: number,
paymentReceiveNumber: string|number,
paymentReceiveId: number
) {
const paymentReceives = await PaymentReceive.tenant()
.query()
const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentReceives = await PaymentReceive.query()
.where('payment_receive_no', paymentReceiveNumber)
.onBuild((query) => {
if (paymentReceiveId) {
@@ -273,21 +295,25 @@ export default class PaymentReceiveService {
* --------
* - Account receivable -> Debit
* - Payment account [current asset] -> Credit
*
* @async
* @param {number} tenantId - Tenant id.
* @param {IPaymentReceive} paymentReceive
* @param {Number} paymentReceiveId
*/
static async recordPaymentReceiveJournalEntries(
async recordPaymentReceiveJournalEntries(
tenantId: number,
paymentReceive: any,
paymentReceiveId?: number
) {
const { Account, AccountTransaction } = this.tenancy.models(tenantId);
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
const formattedDate = moment(paymentReceive.payment_date).format('YYYY-MM-DD');
const receivableAccount = await AccountsService.getAccountByType(
const receivableAccount = await this.accountsService.getAccountByType(
tenantId,
'accounts_receivable'
);
const accountsDepGraph = await Account.tenant().depGraph().query();
const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph);
const commonJournal = {
debit: 0,
@@ -297,8 +323,7 @@ export default class PaymentReceiveService {
date: formattedDate,
};
if (paymentReceiveId) {
const transactions = await AccountTransaction.tenant()
.query()
const transactions = await AccountTransaction.query()
.whereIn('reference_type', ['PaymentReceive'])
.where('reference_id', paymentReceiveId)
.withGraphFetched('account.type');
@@ -330,15 +355,18 @@ export default class PaymentReceiveService {
/**
* Revert the payment amount of the given invoices ids.
* @async
* @param {number} tenantId - Tenant id.
* @param {Array} revertInvoices
* @return {Promise}
*/
static async revertInvoicePaymentAmount(revertInvoices: any[]) {
async revertInvoicePaymentAmount(tenantId: number, revertInvoices: any[]) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const opers: Promise<T>[] = [];
revertInvoices.forEach((revertInvoice) => {
const { revertAmount, invoiceId } = revertInvoice;
const oper = SaleInvoice.tenant()
.query()
const oper = SaleInvoice.query()
.where('id', invoiceId)
.decrement('payment_amount', revertAmount);
opers.push(oper);
@@ -348,14 +376,18 @@ export default class PaymentReceiveService {
/**
* Saves difference changing between old and new invoice payment amount.
* @async
* @param {number} tenantId - Tenant id.
* @param {Array} paymentReceiveEntries
* @param {Array} newPaymentReceiveEntries
* @return
*/
static async saveChangeInvoicePaymentAmount(
async saveChangeInvoicePaymentAmount(
tenantId: number,
paymentReceiveEntries: [],
newPaymentReceiveEntries: [],
) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const opers: Promise<T>[] = [];
const newEntriesTable = chain(newPaymentReceiveEntries)
.groupBy('invoice_id')

View File

@@ -1,31 +1,44 @@
import { omit, difference, sumBy, mixin } from 'lodash';
import moment from 'moment';
import { SaleEstimate, ItemEntry } from '@/models';
import { Service, Inject } from 'typedi';
import HasItemsEntries from '@/services/Sales/HasItemsEntries';
import { formatDateFields } from '@/utils';
import TenancyService from '@/services/Tenancy/TenancyService';
/**
* Sale estimate service.
* @Service
*/
@Service()
export default class SaleEstimateService {
@Inject()
tenancy: TenancyService;
@Inject()
itemsEntriesService: HasItemsEntries;
/**
* Creates a new estimate with associated entries.
* @async
* @param {number} tenantId - The tenant id.
* @param {EstimateDTO} estimate
* @return {void}
*/
static async createEstimate(estimateDTO: any) {
async createEstimate(tenantId: number, estimateDTO: any) {
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
const amount = sumBy(estimateDTO.entries, e => ItemEntry.calcAmount(e));
const estimate = {
amount: sumBy(estimateDTO.entries, 'amount'),
amount,
...formatDateFields(estimateDTO, ['estimate_date', 'expiration_date']),
};
const storedEstimate = await SaleEstimate.tenant()
.query()
const storedEstimate = await SaleEstimate.query()
.insert({
...omit(estimate, ['entries']),
});
const storeEstimateEntriesOpers: any[] = [];
estimate.entries.forEach((entry: any) => {
const oper = ItemEntry.tenant()
.query()
const oper = ItemEntry.query()
.insert({
reference_type: 'SaleEstimate',
reference_id: storedEstimate.id,
@@ -41,27 +54,33 @@ export default class SaleEstimateService {
/**
* Edit details of the given estimate with associated entries.
* @async
* @param {number} tenantId - The tenant id.
* @param {Integer} estimateId
* @param {EstimateDTO} estimate
* @return {void}
*/
static async editEstimate(estimateId: number, estimateDTO: any) {
async editEstimate(tenantId: number, estimateId: number, estimateDTO: any) {
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
const amount = sumBy(estimateDTO.entries, e => ItemEntry.calcAmount(e));
const estimate = {
amount: sumBy(estimateDTO.entries, 'amount'),
amount,
...formatDateFields(estimateDTO, ['estimate_date', 'expiration_date']),
};
const updatedEstimate = await SaleEstimate.tenant()
.query()
const updatedEstimate = await SaleEstimate.query()
.update({
...omit(estimate, ['entries']),
});
const storedEstimateEntries = await ItemEntry.tenant()
.query()
const storedEstimateEntries = await ItemEntry.query()
.where('reference_id', estimateId)
.where('reference_type', 'SaleEstimate');
const patchItemsEntries = HasItemsEntries.patchItemsEntries(
estimate.entries, storedEstimateEntries, 'SaleEstimate', estimateId
const patchItemsEntries = this.itemsEntriesService.patchItemsEntries(
tenantId,
estimate.entries,
storedEstimateEntries,
'SaleEstimate',
estimateId,
);
return Promise.all([
patchItemsEntries,
@@ -71,32 +90,32 @@ export default class SaleEstimateService {
/**
* Deletes the given estimate id with associated entries.
* @async
* @param {number} tenantId - The tenant id.
* @param {IEstimate} estimateId
* @return {void}
*/
static async deleteEstimate(estimateId: number) {
await ItemEntry.tenant()
.query()
async deleteEstimate(tenantId: number, estimateId: number) {
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
await ItemEntry.query()
.where('reference_id', estimateId)
.where('reference_type', 'SaleEstimate')
.delete();
await SaleEstimate.tenant()
.query()
await SaleEstimate.query()
.where('id', estimateId)
.delete();
}
/**
* Validates the given estimate ID exists.
* @async
* @param {number} tenantId - The tenant id.
* @param {Numeric} estimateId
* @return {Boolean}
*/
static async isEstimateExists(estimateId: number) {
const foundEstimate = await SaleEstimate.tenant()
.query()
async isEstimateExists(estimateId: number) {
const { SaleEstimate } = this.tenancy.models(tenantId);
const foundEstimate = await SaleEstimate.query()
.where('id', estimateId);
return foundEstimate.length !== 0;
}
@@ -104,16 +123,17 @@ export default class SaleEstimateService {
/**
* Validates the given estimate entries IDs.
* @async
* @param {Numeric} estimateId
* @param {number} tenantId - The tenant id.
* @param {Numeric} estimateId - the sale estimate id.
* @param {IEstimate} estimate
*/
static async isEstimateEntriesIDsExists(estimateId: number, estimate: any) {
async isEstimateEntriesIDsExists(tenantId: number, estimateId: number, estimate: any) {
const { ItemEntry } = this.tenancy.models(tenantId);
const estimateEntriesIds = estimate.entries
.filter((e: any) => e.id)
.map((e: any) => e.id);
const estimateEntries = await ItemEntry.tenant()
.query()
const estimateEntries = await ItemEntry.query()
.whereIn('id', estimateEntriesIds)
.where('reference_id', estimateId)
.where('reference_type', 'SaleEstimate');
@@ -128,12 +148,14 @@ export default class SaleEstimateService {
/**
* Retrieve the estimate details of the given estimate id.
* @async
* @param {number} tenantId - The tenant id.
* @param {Integer} estimateId
* @return {IEstimate}
*/
static async getEstimate(estimateId: number) {
const estimate = await SaleEstimate.tenant()
.query()
async getEstimate(tenantId: number, estimateId: number) {
const { SaleEstimate } = this.tenancy.models(tenantId);
const estimate = await SaleEstimate.query()
.where('id', estimateId)
.first();
@@ -142,11 +164,13 @@ export default class SaleEstimateService {
/**
* Retrieve the estimate details with associated entries.
* @async
* @param {number} tenantId - The tenant id.
* @param {Integer} estimateId
*/
static async getEstimateWithEntries(estimateId: number) {
const estimate = await SaleEstimate.tenant()
.query()
async getEstimateWithEntries(tenantId: number, estimateId: number) {
const { SaleEstimate } = this.tenancy.models(tenantId);
const estimate = await SaleEstimate.query()
.where('id', estimateId)
.withGraphFetched('entries')
.withGraphFetched('customer')
@@ -157,13 +181,15 @@ export default class SaleEstimateService {
/**
* Detarmines the estimate number uniqness.
* @async
* @param {number} tenantId - The tenant id.
* @param {String} estimateNumber
* @param {Integer} excludeEstimateId
* @return {Boolean}
*/
static async isEstimateNumberUnique(estimateNumber: string, excludeEstimateId: number) {
const foundEstimates = await SaleEstimate.tenant()
.query()
async isEstimateNumberUnique(tenantId: number, estimateNumber: string, excludeEstimateId: number) {
const { SaleEstimate } = this.tenancy.models(tenantId);
const foundEstimates = await SaleEstimate.query()
.onBuild((query: any) => {
query.where('estimate_number', estimateNumber);

View File

@@ -1,52 +1,56 @@
import { Service, Inject } from 'typedi';
import { omit, sumBy, difference, pick, chain } from 'lodash';
import {
SaleInvoice,
AccountTransaction,
InventoryTransaction,
Account,
ItemEntry,
Customer,
Item,
} from '@/models';
import { ISaleInvoice, ISaleInvoiceOTD, IItemEntry } from '@/interfaces';
import JournalPoster from '@/services/Accounting/JournalPoster';
import HasItemsEntries from '@/services/Sales/HasItemsEntries';
import CustomerRepository from '@/repositories/CustomerRepository';
import InventoryService from '@/services/Inventory/Inventory';
import SalesInvoicesCost from '@/services/Sales/SalesInvoicesCost';
import { ISaleInvoice, ISaleInvoiceOTD, IItemEntry } from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import { formatDateFields } from '@/utils';
/**
* Sales invoices service
* @service
*/
@Service()
export default class SaleInvoicesService extends SalesInvoicesCost {
@Inject()
tenancy: TenancyService;
@Inject()
inventoryService: InventoryService;
@Inject()
itemsEntriesService: HasItemsEntries;
/**
* Creates a new sale invoices and store it to the storage
* with associated to entries and journal transactions.
* @async
* @param {ISaleInvoice}
* @param {number} tenantId =
* @param {ISaleInvoice} saleInvoiceDTO -
* @return {ISaleInvoice}
*/
static async createSaleInvoice(saleInvoiceDTO: ISaleInvoiceOTD) {
const balance = sumBy(saleInvoiceDTO.entries, 'amount');
const invLotNumber = await InventoryService.nextLotNumber();
async createSaleInvoice(tenantId: number, saleInvoiceDTO: ISaleInvoiceOTD) {
const { SaleInvoice, Customer, ItemEntry } = this.tenancy.models(tenantId);
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
const invLotNumber = await this.inventoryService.nextLotNumber(tenantId);
const saleInvoice: ISaleInvoice = {
...formatDateFields(saleInvoiceDTO, ['invoiceDate', 'dueDate']),
...formatDateFields(saleInvoiceDTO, ['invoice_date', 'due_date']),
balance,
paymentAmount: 0,
invLotNumber,
};
const storedInvoice = await SaleInvoice.tenant()
.query()
const storedInvoice = await SaleInvoice.query()
.insert({
...omit(saleInvoice, ['entries']),
});
const opers: Array<any> = [];
saleInvoice.entries.forEach((entry: any) => {
const oper = ItemEntry.tenant()
.query()
const oper = ItemEntry.query()
.insertAndFetch({
reference_type: 'SaleInvoice',
reference_id: storedInvoice.id,
@@ -67,12 +71,11 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
...opers, incrementOper,
]);
// Records the inventory transactions for inventory items.
await this.recordInventoryTranscactions(
saleInvoice, storedInvoice.id
);
await this.recordInventoryTranscactions(tenantId, saleInvoice, storedInvoice.id);
// Schedule sale invoice re-compute based on the item cost
// method and starting date.
await this.scheduleComputeInvoiceItemsCost(storedInvoice.id);
await this.scheduleComputeInvoiceItemsCost(tenantId, storedInvoice.id);
return storedInvoice;
}
@@ -80,12 +83,15 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
/**
* Edit the given sale invoice.
* @async
* @param {number} tenantId -
* @param {Number} saleInvoiceId -
* @param {ISaleInvoice} saleInvoice -
*/
static async editSaleInvoice(saleInvoiceId: number, saleInvoiceDTO: any) {
const balance = sumBy(saleInvoiceDTO.entries, 'amount');
const oldSaleInvoice = await SaleInvoice.tenant().query()
async editSaleInvoice(tenantId: number, saleInvoiceId: number, saleInvoiceDTO: any) {
const { SaleInvoice, ItemEntry, Customer } = this.tenancy.models(tenantId);
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
const oldSaleInvoice = await SaleInvoice.query()
.where('id', saleInvoiceId)
.first();
@@ -94,24 +100,22 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
balance,
invLotNumber: oldSaleInvoice.invLotNumber,
};
const updatedSaleInvoices: ISaleInvoice = await SaleInvoice.tenant()
.query()
const updatedSaleInvoices: ISaleInvoice = await SaleInvoice.query()
.where('id', saleInvoiceId)
.update({
...omit(saleInvoice, ['entries', 'invLotNumber']),
});
// Fetches the sale invoice items entries.
const storedEntries = await ItemEntry.tenant()
.query()
const storedEntries = await ItemEntry.query()
.where('reference_id', saleInvoiceId)
.where('reference_type', 'SaleInvoice');
// Patch update the sale invoice items entries.
const patchItemsEntriesOper = HasItemsEntries.patchItemsEntries(
saleInvoice.entries, storedEntries, 'SaleInvoice', saleInvoiceId,
const patchItemsEntriesOper = this.itemsEntriesService.patchItemsEntries(
tenantId, saleInvoice.entries, storedEntries, 'SaleInvoice', saleInvoiceId,
);
// Changes the diff customer balance between old and new amount.
const changeCustomerBalanceOper = CustomerRepository.changeDiffBalance(
const changeCustomerBalanceOper = Customer.changeDiffBalance(
saleInvoice.customer_id,
oldSaleInvoice.customerId,
balance,
@@ -119,7 +123,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
);
// Records the inventory transactions for inventory items.
const recordInventoryTransOper = this.recordInventoryTranscactions(
saleInvoice, saleInvoiceId, true,
tenantId, saleInvoice, saleInvoiceId, true,
);
await Promise.all([
patchItemsEntriesOper,
@@ -128,23 +132,31 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
]);
// Schedule sale invoice re-compute based on the item cost
// method and starting date.
await this.scheduleComputeInvoiceItemsCost(saleInvoiceId, true);
await this.scheduleComputeInvoiceItemsCost(tenantId, saleInvoiceId, true);
}
/**
* Deletes the given sale invoice with associated entries
* and journal transactions.
* @async
* @param {Number} saleInvoiceId
* @param {Number} saleInvoiceId - The given sale invoice id.
*/
static async deleteSaleInvoice(saleInvoiceId: number) {
const oldSaleInvoice = await SaleInvoice.tenant().query()
async deleteSaleInvoice(tenantId: number, saleInvoiceId: number) {
const {
SaleInvoice,
ItemEntry,
Customer,
Account,
InventoryTransaction,
AccountTransaction,
} = this.tenancy.models(tenantId);
const oldSaleInvoice = await SaleInvoice.query()
.findById(saleInvoiceId)
.withGraphFetched('entries');
await SaleInvoice.tenant().query().where('id', saleInvoiceId).delete();
await ItemEntry.tenant()
.query()
await SaleInvoice.query().where('id', saleInvoiceId).delete();
await ItemEntry.query()
.where('reference_id', saleInvoiceId)
.where('reference_type', 'SaleInvoice')
.delete();
@@ -153,26 +165,25 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
oldSaleInvoice.customerId,
oldSaleInvoice.balance * -1,
);
const invoiceTransactions = await AccountTransaction.tenant()
.query()
const invoiceTransactions = await AccountTransaction.query()
.whereIn('reference_type', ['SaleInvoice'])
.where('reference_id', saleInvoiceId)
.withGraphFetched('account.type');
const accountsDepGraph = await Account.tenant().depGraph().query();
const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph);
journal.loadEntries(invoiceTransactions);
journal.removeEntries();
const inventoryTransactions = await InventoryTransaction.tenant()
.query()
const inventoryTransactions = await InventoryTransaction.query()
.where('transaction_type', 'SaleInvoice')
.where('transaction_id', saleInvoiceId);
// Revert inventory transactions.
const revertInventoryTransactionsOper = this.revertInventoryTransactions(
inventoryTransactions
tenantId,
inventoryTransactions,
);
// Await all async operations.
await Promise.all([
@@ -183,7 +194,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
]);
// Schedule sale invoice re-compute based on the item cost
// method and starting date.
await this.scheduleComputeItemsCost(oldSaleInvoice)
await this.scheduleComputeItemsCost(tenantId, oldSaleInvoice)
}
/**
@@ -192,7 +203,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {number} saleInvoiceId -
* @param {boolean} override -
*/
static recordInventoryTranscactions(saleInvoice, saleInvoiceId: number, override?: boolean){
recordInventoryTranscactions(tenantId: number, saleInvoice, saleInvoiceId: number, override?: boolean){
const inventortyTransactions = saleInvoice.entries
.map((entry) => ({
...pick(entry, ['item_id', 'quantity', 'rate',]),
@@ -204,8 +215,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
entryId: entry.id,
}));
return InventoryService.recordInventoryTransactions(
inventortyTransactions, override,
return this.inventoryService.recordInventoryTransactions(
tenantId, inventortyTransactions, override,
);
}
@@ -214,15 +225,15 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {string} transactionType
* @param {number} transactionId
*/
static async revertInventoryTransactions(inventoryTransactions: array) {
async revertInventoryTransactions(tenantId: number, inventoryTransactions: array) {
const { InventoryTransaction } = this.tenancy.models(tenantId);
const opers: Promise<[]>[] = [];
inventoryTransactions.forEach((trans: any) => {
switch(trans.direction) {
case 'OUT':
if (trans.inventoryTransactionId) {
const revertRemaining = InventoryTransaction.tenant()
.query()
const revertRemaining = InventoryTransaction.query()
.where('id', trans.inventoryTransactionId)
.where('direction', 'OUT')
.increment('remaining', trans.quanitity);
@@ -231,8 +242,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
}
break;
case 'IN':
const removeRelationOper = InventoryTransaction.tenant()
.query()
const removeRelationOper = InventoryTransaction.query()
.where('inventory_transaction_id', trans.id)
.where('direction', 'IN')
.update({
@@ -250,8 +260,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @async
* @param {Number} saleInvoiceId
*/
static async getSaleInvoiceWithEntries(saleInvoiceId: number) {
return SaleInvoice.tenant().query()
async getSaleInvoiceWithEntries(tenantId: number, saleInvoiceId: number) {
const { SaleInvoice } = this.tenancy.models(tenantId);
return SaleInvoice.query()
.where('id', saleInvoiceId)
.withGraphFetched('entries')
.withGraphFetched('customer')
@@ -263,10 +274,11 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {Integer} saleInvoiceId
* @return {Boolean}
*/
static async isSaleInvoiceExists(saleInvoiceId: number) {
const foundSaleInvoice = await SaleInvoice.tenant()
.query()
async isSaleInvoiceExists(tenantId: number, saleInvoiceId: number) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const foundSaleInvoice = await SaleInvoice.query()
.where('id', saleInvoiceId);
return foundSaleInvoice.length !== 0;
}
@@ -277,12 +289,15 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {Number} saleInvoiceId
* @return {Boolean}
*/
static async isSaleInvoiceNumberExists(saleInvoiceNumber: string|number, saleInvoiceId: number) {
const foundSaleInvoice = await SaleInvoice.tenant()
.query()
async isSaleInvoiceNumberExists(
tenantId: number,
saleInvoiceNumber: string|number,
saleInvoiceId: number
) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const foundSaleInvoice = await SaleInvoice.query()
.onBuild((query: any) => {
query.where('invoice_no', saleInvoiceNumber);
if (saleInvoiceId) {
query.whereNot('id', saleInvoiceId);
}
@@ -296,9 +311,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {Array} invoicesIds
* @return {Array}
*/
static async isInvoicesExist(invoicesIds: Array<number>) {
const storedInvoices = await SaleInvoice.tenant()
.query()
async isInvoicesExist(tenantId: number, invoicesIds: Array<number>) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const storedInvoices = await SaleInvoice.query()
.onBuild((builder: any) => {
builder.whereIn('id', invoicesIds);
return builder;
@@ -314,9 +329,13 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {ISaleInvoice} saleInvoice
* @return {Promise}
*/
static async scheduleComputeInvoiceItemsCost(saleInvoiceId: number, override?: boolean) {
const saleInvoice: ISaleInvoice = await SaleInvoice.tenant()
.query()
async scheduleComputeInvoiceItemsCost(
tenantId: number,
saleInvoiceId: number,
override?: boolean
) {
const { SaleInvoice } = this.tenancy.models(tenantId);
const saleInvoice: ISaleInvoice = await SaleInvoice.query()
.findById(saleInvoiceId)
.withGraphFetched('entries.item');
@@ -326,9 +345,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
.uniq().value();
if (inventoryItemsIds.length === 0) {
await this.writeNonInventoryInvoiceJournals(saleInvoice, override);
await this.writeNonInventoryInvoiceJournals(tenantId, saleInvoice, override);
} else {
await this.scheduleComputeItemsCost(
tenantId,
inventoryItemsIds,
saleInvoice.invoice_date,
);
@@ -339,13 +359,14 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* Writes the sale invoice journal entries.
* @param {SaleInvoice} saleInvoice -
*/
static async writeNonInventoryInvoiceJournals(saleInvoice: ISaleInvoice, override: boolean) {
const accountsDepGraph = await Account.tenant().depGraph().query();
async writeNonInventoryInvoiceJournals(tenantId: number, saleInvoice: ISaleInvoice, override: boolean) {
const { Account, AccountTransaction } = this.tenancy.models(tenantId);
const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph);
if (override) {
const oldTransactions = await AccountTransaction.tenant()
.query()
const oldTransactions = await AccountTransaction.query()
.where('reference_type', 'SaleInvoice')
.where('reference_id', saleInvoice.id)
.withGraphFetched('account.type');

View File

@@ -1,17 +1,18 @@
import { Container } from 'typedi';
import {
SaleInvoice,
Account,
AccountTransaction,
Item,
} from '@/models';
import { Container, Service, Inject } from 'typedi';
import JournalPoster from '@/services/Accounting/JournalPoster';
import JournalEntry from '@/services/Accounting/JournalEntry';
import InventoryService from '@/services/Inventory/Inventory';
import { ISaleInvoice, IItemEntry, IItem } from '@/interfaces';
import { ISaleInvoice } from '../../interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import { ISaleInvoice, IItemEntry } from '@/interfaces';
@Service()
export default class SaleInvoicesCost {
@Inject()
inventoryService: InventoryService;
@Inject()
tenancy: TenancyService;
/**
* Schedule sale invoice re-compute based on the item
* cost method and starting date.
@@ -19,11 +20,16 @@ export default class SaleInvoicesCost {
* @param {Date} startingDate - Starting compute cost date.
* @return {Promise<Agenda>}
*/
static async scheduleComputeItemsCost(inventoryItemsIds: number[], startingDate: Date) {
async scheduleComputeItemsCost(
tenantId: number,
inventoryItemsIds: number[],
startingDate: Date
) {
const asyncOpers: Promise<[]>[] = [];
inventoryItemsIds.forEach((inventoryItemId: number) => {
const oper: Promise<[]> = InventoryService.scheduleComputeItemCost(
const oper: Promise<[]> = this.inventoryService.scheduleComputeItemCost(
tenantId,
inventoryItemId,
startingDate,
);
@@ -37,21 +43,22 @@ export default class SaleInvoicesCost {
* @param {Date} startingDate
* @return {Promise<agenda>}
*/
static scheduleWriteJournalEntries(startingDate?: Date) {
scheduleWriteJournalEntries(tenantId: number, startingDate?: Date) {
const agenda = Container.get('agenda');
return agenda.schedule('in 3 seconds', 'rewrite-invoices-journal-entries', {
startingDate,
startingDate, tenantId,
});
}
/**
* Writes journal entries from sales invoices.
* @param {number} tenantId - The tenant id.
* @param {Date} startingDate
* @param {boolean} override
*/
static async writeJournalEntries(startingDate: Date, override: boolean) {
const salesInvoices = await SaleInvoice.tenant()
.query()
async writeJournalEntries(tenantId: number, startingDate: Date, override: boolean) {
const { AccountTransaction, SaleInvoice, Account } = this.tenancy.models(tenantId);
const salesInvoices = await SaleInvoice.query()
.onBuild((builder: any) => {
builder.modify('filterDateRange', startingDate);
builder.orderBy('invoice_date', 'ASC');
@@ -59,12 +66,11 @@ export default class SaleInvoicesCost {
builder.withGraphFetched('entries.item')
builder.withGraphFetched('costTransactions(groupedEntriesCost)');
});
const accountsDepGraph = await Account.tenant().depGraph().query();
const accountsDepGraph = await Account.depGraph().query();
const journal = new JournalPoster(accountsDepGraph);
if (override) {
const oldTransactions = await AccountTransaction.tenant()
.query()
const oldTransactions = await AccountTransaction.query()
.whereIn('reference_type', ['SaleInvoice'])
.onBuild((builder: any) => {
builder.modify('filterDateRange', startingDate);
@@ -90,7 +96,7 @@ export default class SaleInvoicesCost {
* @param {ISaleInvoice} saleInvoice
* @param {JournalPoster} journal
*/
static saleInvoiceJournal(saleInvoice: ISaleInvoice, journal: JournalPoster) {
saleInvoiceJournal(saleInvoice: ISaleInvoice, journal: JournalPoster) {
let inventoryTotal: number = 0;
const receivableAccount = { id: 10 };
const commonEntry = {

View File

@@ -1,36 +1,43 @@
import { omit, difference, sumBy } from 'lodash';
import {
SaleReceipt,
Account,
ItemEntry,
} from '@/models';
import JournalPoster from '@/services/Accounting/JournalPoster';
import { Service, Inject } from 'typedi';
import JournalPosterService from '@/services/Sales/JournalPosterService';
import HasItemEntries from '@/services/Sales/HasItemsEntries';
import TenancyService from '@/services/Tenancy/TenancyService';
import { formatDateFields } from '@/utils';
export default class SalesReceipt {
@Service()
export default class SalesReceiptService {
@Inject()
tenancy: TenancyService;
@Inject()
journalService: JournalPosterService;
@Inject()
itemsEntriesService: HasItemEntries;
/**
* Creates a new sale receipt with associated entries.
* @async
* @param {ISaleReceipt} saleReceipt
* @return {Object}
*/
static async createSaleReceipt(saleReceiptDTO: any) {
async createSaleReceipt(tenantId: number, saleReceiptDTO: any) {
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
const amount = sumBy(saleReceiptDTO.entries, e => ItemEntry.calcAmount(e));
const saleReceipt = {
amount: sumBy(saleReceiptDTO.entries, 'amount');
amount,
...formatDateFields(saleReceiptDTO, ['receipt_date'])
};
const storedSaleReceipt = await SaleReceipt.tenant()
.query()
const storedSaleReceipt = await SaleReceipt.query()
.insert({
...omit(saleReceipt, ['entries']),
});
const storeSaleReceiptEntriesOpers: Array<any> = [];
saleReceipt.entries.forEach((entry: any) => {
const oper = ItemEntry.tenant()
.query()
const oper = ItemEntry.query()
.insert({
reference_type: 'SaleReceipt',
reference_id: storedSaleReceipt.id,
@@ -48,25 +55,30 @@ export default class SalesReceipt {
* @param {ISaleReceipt} saleReceipt
* @return {void}
*/
static async editSaleReceipt(saleReceiptId: number, saleReceiptDTO: any) {
async editSaleReceipt(tenantId: number, saleReceiptId: number, saleReceiptDTO: any) {
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
const amount = sumBy(saleReceiptDTO.entries, e => ItemEntry.calcAmount(e));
const saleReceipt = {
amount: sumBy(saleReceiptDTO.entries, 'amount'),
amount,
...formatDateFields(saleReceiptDTO, ['receipt_date'])
};
const updatedSaleReceipt = await SaleReceipt.tenant()
.query()
const updatedSaleReceipt = await SaleReceipt.query()
.where('id', saleReceiptId)
.update({
...omit(saleReceipt, ['entries']),
});
const storedSaleReceiptEntries = await ItemEntry.tenant()
.query()
const storedSaleReceiptEntries = await ItemEntry.query()
.where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt');
// Patch sale receipt items entries.
const patchItemsEntries = HasItemEntries.patchItemsEntries(
saleReceipt.entries, storedSaleReceiptEntries, 'SaleReceipt', saleReceiptId,
const patchItemsEntries = this.itemsEntriesService.patchItemsEntries(
tenantId,
saleReceipt.entries,
storedSaleReceiptEntries,
'SaleReceipt',
saleReceiptId,
);
return Promise.all([patchItemsEntries]);
}
@@ -76,20 +88,20 @@ export default class SalesReceipt {
* @param {Integer} saleReceiptId
* @return {void}
*/
static async deleteSaleReceipt(saleReceiptId: number) {
const deleteSaleReceiptOper = SaleReceipt.tenant()
.query()
async deleteSaleReceipt(tenantId: number, saleReceiptId: number) {
const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId);
const deleteSaleReceiptOper = SaleReceipt.query()
.where('id', saleReceiptId)
.delete();
const deleteItemsEntriesOper = ItemEntry.tenant()
.query()
const deleteItemsEntriesOper = ItemEntry.query()
.where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt')
.delete();
// Delete all associated journal transactions to payment receive transaction.
const deleteTransactionsOper = JournalPosterService.deleteJournalTransactions(
const deleteTransactionsOper = this.journalService.deleteJournalTransactions(
tenantId,
saleReceiptId,
'SaleReceipt'
);
@@ -105,9 +117,9 @@ export default class SalesReceipt {
* @param {Integer} saleReceiptId
* @returns {Boolean}
*/
static async isSaleReceiptExists(saleReceiptId: number) {
const foundSaleReceipt = await SaleReceipt.tenant()
.query()
async isSaleReceiptExists(tenantId: number, saleReceiptId: number) {
const { SaleReceipt } = this.tenancy.models(tenantId);
const foundSaleReceipt = await SaleReceipt.query()
.where('id', saleReceiptId);
return foundSaleReceipt.length !== 0;
}
@@ -117,13 +129,13 @@ export default class SalesReceipt {
* @param {Integer} saleReceiptId
* @param {ISaleReceipt} saleReceipt
*/
static async isSaleReceiptEntriesIDsExists(saleReceiptId: number, saleReceipt: any) {
async isSaleReceiptEntriesIDsExists(tenantId: number, saleReceiptId: number, saleReceipt: any) {
const { ItemEntry } = this.tenancy.models(tenantId);
const entriesIDs = saleReceipt.entries
.filter((e) => e.id)
.map((e) => e.id);
const storedEntries = await ItemEntry.tenant()
.query()
const storedEntries = await ItemEntry.query()
.whereIn('id', entriesIDs)
.where('reference_id', saleReceiptId)
.where('reference_type', 'SaleReceipt');
@@ -140,21 +152,12 @@ export default class SalesReceipt {
* Retrieve sale receipt with associated entries.
* @param {Integer} saleReceiptId
*/
static async getSaleReceiptWithEntries(saleReceiptId: number) {
const saleReceipt = await SaleReceipt.tenant().query()
async getSaleReceiptWithEntries(tenantId: number, saleReceiptId: number) {
const { SaleReceipt } = this.tenancy.models(tenantId);
const saleReceipt = await SaleReceipt.query()
.where('id', saleReceiptId)
.withGraphFetched('entries');
return saleReceipt;
}
/**
* Records journal transactions for sale receipt.
* @param {ISaleReceipt} saleReceipt
* @return {Promise}
*/
static async _recordJournalTransactions(saleReceipt: any) {
const accountsDepGraph = await Account.tenant().depGraph().query();
const journalPoster = new JournalPoster(accountsDepGraph);
}
}

View File

@@ -0,0 +1,21 @@
import { Container } from 'typedi';
export default class HasTenancyService {
/**
* Retrieve the given tenant container.
* @param {number} tenantId
* @return {Container}
*/
tenantContainer(tenantId: number) {
return Container.of(`tenant-${tenantId}`);
}
/**
* Retrieve models of the givne tenant id.
* @param {number} tenantId - The tenant id.
*/
models(tenantId: number) {
console.log(tenantId);
return this.tenantContainer(tenantId).get('models');
}
}