mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
feat(nestjs): migrate to NestJS
This commit is contained in:
@@ -1,199 +0,0 @@
|
||||
import { ACCOUNT_TYPES } from '@/data/AccountTypes';
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
importable: true,
|
||||
exportable: true,
|
||||
print: {
|
||||
pageTitle: 'Chart of Accounts',
|
||||
},
|
||||
fields: {
|
||||
name: {
|
||||
name: 'account.field.name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'account.field.description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
slug: {
|
||||
name: 'account.field.slug',
|
||||
column: 'slug',
|
||||
fieldType: 'text',
|
||||
columnable: false,
|
||||
filterable: false,
|
||||
},
|
||||
code: {
|
||||
name: 'account.field.code',
|
||||
column: 'code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
root_type: {
|
||||
name: 'account.field.root_type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'asset', label: 'Asset' },
|
||||
{ key: 'liability', label: 'Liability' },
|
||||
{ key: 'equity', label: 'Equity' },
|
||||
{ key: 'Income', label: 'Income' },
|
||||
{ key: 'expense', label: 'Expense' },
|
||||
],
|
||||
filterCustomQuery: RootTypeFieldFilterQuery,
|
||||
sortable: false,
|
||||
},
|
||||
normal: {
|
||||
name: 'account.field.normal',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'debit', label: 'account.field.normal.debit' },
|
||||
{ key: 'credit', label: 'account.field.normal.credit' },
|
||||
],
|
||||
filterCustomQuery: NormalTypeFieldFilterQuery,
|
||||
sortable: false,
|
||||
},
|
||||
type: {
|
||||
name: 'account.field.type',
|
||||
column: 'account_type',
|
||||
fieldType: 'enumeration',
|
||||
options: ACCOUNT_TYPES.map((accountType) => ({
|
||||
label: accountType.label,
|
||||
key: accountType.key,
|
||||
})),
|
||||
},
|
||||
active: {
|
||||
name: 'account.field.active',
|
||||
column: 'active',
|
||||
fieldType: 'boolean',
|
||||
filterable: false,
|
||||
},
|
||||
balance: {
|
||||
name: 'account.field.balance',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currency: {
|
||||
name: 'account.field.currency',
|
||||
column: 'currency_code',
|
||||
fieldType: 'text',
|
||||
filterable: false,
|
||||
},
|
||||
created_at: {
|
||||
name: 'account.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
name: {
|
||||
name: 'account.field.name',
|
||||
type: 'text',
|
||||
},
|
||||
code: {
|
||||
name: 'account.field.code',
|
||||
type: 'text',
|
||||
},
|
||||
rootType: {
|
||||
name: 'account.field.root_type',
|
||||
type: 'text',
|
||||
accessor: 'accountRootType',
|
||||
},
|
||||
accountType: {
|
||||
name: 'account.field.type',
|
||||
accessor: 'accountTypeLabel',
|
||||
type: 'text',
|
||||
},
|
||||
accountNormal: {
|
||||
name: 'account.field.normal',
|
||||
accessor: 'accountNormalFormatted',
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'account.field.currency',
|
||||
type: 'text',
|
||||
},
|
||||
bankBalance: {
|
||||
name: 'account.field.bank_balance',
|
||||
accessor: 'bankBalanceFormatted',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
balance: {
|
||||
name: 'account.field.balance',
|
||||
accessor: 'formattedAmount',
|
||||
},
|
||||
description: {
|
||||
name: 'account.field.description',
|
||||
type: 'text',
|
||||
},
|
||||
active: {
|
||||
name: 'account.field.active',
|
||||
type: 'boolean',
|
||||
},
|
||||
createdAt: {
|
||||
name: 'account.field.created_at',
|
||||
printable: false,
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
name: {
|
||||
name: 'account.field.name',
|
||||
fieldType: 'text',
|
||||
unique: true,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
name: 'account.field.description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
code: {
|
||||
name: 'account.field.code',
|
||||
fieldType: 'text',
|
||||
minLength: 3,
|
||||
maxLength: 6,
|
||||
unique: true,
|
||||
importHint: 'Unique number to identify the account.',
|
||||
},
|
||||
accountType: {
|
||||
name: 'account.field.type',
|
||||
fieldType: 'enumeration',
|
||||
options: ACCOUNT_TYPES.map((accountType) => ({
|
||||
label: accountType.label,
|
||||
key: accountType.key,
|
||||
})),
|
||||
required: true,
|
||||
},
|
||||
active: {
|
||||
name: 'account.field.active',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'account.field.currency',
|
||||
fieldType: 'text',
|
||||
},
|
||||
parentAccountId: {
|
||||
name: 'account.field.parent_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter query of root type field .
|
||||
*/
|
||||
function RootTypeFieldFilterQuery(query, role) {
|
||||
query.modify('filterByRootType', role.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query of normal field .
|
||||
*/
|
||||
function NormalTypeFieldFilterQuery(query, role) {
|
||||
query.modify('filterByAccountNormal', role.value);
|
||||
}
|
||||
@@ -1,448 +0,0 @@
|
||||
/* eslint-disable global-require */
|
||||
import { mixin, Model } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import TenantModel from '@/models/TenantModel';
|
||||
import { buildFilterQuery, buildSortColumnQuery } from '@/lib/ViewRolesBuilder';
|
||||
import { flatToNestedArray } from 'utils';
|
||||
import DependencyGraph from '@/lib/DependencyGraph';
|
||||
import AccountTypesUtils from '@/lib/AccountTypes';
|
||||
import AccountSettings from './Account.Settings';
|
||||
import ModelSettings from './ModelSetting';
|
||||
import {
|
||||
ACCOUNT_TYPES,
|
||||
getAccountsSupportsMultiCurrency,
|
||||
} from '@/data/AccountTypes';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Accounts/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class Account extends mixin(TenantModel, [
|
||||
ModelSettings,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'accounts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'accountTypeLabel',
|
||||
'accountParentType',
|
||||
'accountRootType',
|
||||
'accountNormal',
|
||||
'accountNormalFormatted',
|
||||
'isBalanceSheetAccount',
|
||||
'isPLSheet',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Account normal.
|
||||
*/
|
||||
get accountNormal() {
|
||||
return AccountTypesUtils.getType(this.accountType, 'normal');
|
||||
}
|
||||
|
||||
get accountNormalFormatted() {
|
||||
const paris = {
|
||||
credit: 'Credit',
|
||||
debit: 'Debit',
|
||||
};
|
||||
return paris[this.accountNormal] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account type label.
|
||||
*/
|
||||
get accountTypeLabel() {
|
||||
return AccountTypesUtils.getType(this.accountType, 'label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account parent type.
|
||||
*/
|
||||
get accountParentType() {
|
||||
return AccountTypesUtils.getType(this.accountType, 'parentType');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account root type.
|
||||
*/
|
||||
get accountRootType() {
|
||||
return AccountTypesUtils.getType(this.accountType, 'rootType');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether the account is balance sheet account.
|
||||
*/
|
||||
get isBalanceSheetAccount() {
|
||||
return this.isBalanceSheet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether the account is profit/loss sheet account.
|
||||
*/
|
||||
get isPLSheet() {
|
||||
return this.isProfitLossSheet();
|
||||
}
|
||||
/**
|
||||
* Allows to mark model as resourceable to viewable and filterable.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
const TABLE_NAME = Account.tableName;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('accounts.active', !active);
|
||||
},
|
||||
|
||||
filterAccounts(query, accountIds) {
|
||||
if (accountIds.length > 0) {
|
||||
query.whereIn(`${TABLE_NAME}.id`, accountIds);
|
||||
}
|
||||
},
|
||||
filterAccountTypes(query, typesIds) {
|
||||
if (typesIds.length > 0) {
|
||||
query.whereIn('account_types.account_type_id', typesIds);
|
||||
}
|
||||
},
|
||||
viewRolesBuilder(query, conditionals, expression) {
|
||||
buildFilterQuery(Account.tableName, conditionals, expression)(query);
|
||||
},
|
||||
sortColumnBuilder(query, columnKey, direction) {
|
||||
buildSortColumnQuery(Account.tableName, columnKey, direction)(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by root type.
|
||||
*/
|
||||
filterByRootType(query, rootType) {
|
||||
const filterTypes = ACCOUNT_TYPES.filter(
|
||||
(accountType) => accountType.rootType === rootType
|
||||
).map((accountType) => accountType.key);
|
||||
|
||||
query.whereIn('account_type', filterTypes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by account normal
|
||||
*/
|
||||
filterByAccountNormal(query, accountNormal) {
|
||||
const filterTypes = ACCOUNT_TYPES.filter(
|
||||
(accountType) => accountType.normal === accountNormal
|
||||
).map((accountType) => accountType.key);
|
||||
|
||||
query.whereIn('account_type', filterTypes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds account by the given slug.
|
||||
* @param {*} query
|
||||
* @param {*} slug
|
||||
*/
|
||||
findBySlug(query, slug) {
|
||||
query.where('slug', slug).first();
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} baseCyrrency
|
||||
*/
|
||||
preventMutateBaseCurrency(query) {
|
||||
const accountsTypes = getAccountsSupportsMultiCurrency();
|
||||
const accountsTypesKeys = accountsTypes.map((type) => type.key);
|
||||
|
||||
query
|
||||
.whereIn('accountType', accountsTypesKeys)
|
||||
.where('seededAt', null)
|
||||
.first();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const Item = require('models/Item');
|
||||
const InventoryAdjustment = require('models/InventoryAdjustment');
|
||||
const ManualJournalEntry = require('models/ManualJournalEntry');
|
||||
const Expense = require('models/Expense');
|
||||
const ExpenseEntry = require('models/ExpenseCategory');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction');
|
||||
const PlaidItem = require('models/PlaidItem');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Account model may has many transactions.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'accounts_transactions.accountId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
itemsCostAccount: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Item.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'items.costAccountId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
itemsSellAccount: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Item.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'items.sellAccountId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
inventoryAdjustments: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: InventoryAdjustment.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'inventory_adjustments.adjustmentAccountId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
manualJournalEntries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ManualJournalEntry.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'manual_journals_entries.accountId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
expensePayments: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Expense.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'expenses_transactions.paymentAccountId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
expenseEntries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ExpenseEntry.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'expense_transaction_categories.expenseAccountId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
entriesCostAccount: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'items_entries.costAccountId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
entriesSellAccount: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'items_entries.sellAccountId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Associated uncategorized transactions.
|
||||
*/
|
||||
uncategorizedTransactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: UncategorizedTransaction.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'uncategorized_cashflow_transactions.accountId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.where('categorized', false);
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Account model may belongs to a Plaid item.
|
||||
*/
|
||||
plaidItem: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PlaidItem.default,
|
||||
join: {
|
||||
from: 'accounts.plaidItemId',
|
||||
to: 'plaid_items.plaidItemId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the given type equals the account type.
|
||||
* @param {string} accountType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAccountType(accountType) {
|
||||
const types = castArray(accountType);
|
||||
return types.indexOf(this.accountType) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the given root type equals the account type.
|
||||
* @param {string} rootType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isRootType(rootType) {
|
||||
return AccountTypesUtils.isRootTypeEqualsKey(this.accountType, rootType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmine whether the given parent type equals the account type.
|
||||
* @param {string} parentType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isParentType(parentType) {
|
||||
return AccountTypesUtils.isParentTypeEqualsKey(
|
||||
this.accountType,
|
||||
parentType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the account is balance sheet account.
|
||||
* @return {boolean}
|
||||
*/
|
||||
isBalanceSheet() {
|
||||
return AccountTypesUtils.isTypeBalanceSheet(this.accountType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the account is profit/loss account.
|
||||
* @return {boolean}
|
||||
*/
|
||||
isProfitLossSheet() {
|
||||
return AccountTypesUtils.isTypePLSheet(this.accountType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the account is income statement account
|
||||
* @return {boolean}
|
||||
*/
|
||||
isIncomeSheet() {
|
||||
return this.isProfitLossSheet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts flatten accounts list to nested array.
|
||||
* @param {Array} accounts
|
||||
* @param {Object} options
|
||||
*/
|
||||
static toNestedArray(accounts, options = { children: 'children' }) {
|
||||
return flatToNestedArray(accounts, {
|
||||
id: 'id',
|
||||
parentId: 'parentAccountId',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes the accounts list to depenedency graph structure.
|
||||
* @param {IAccount[]} accounts
|
||||
*/
|
||||
static toDependencyGraph(accounts) {
|
||||
return DependencyGraph.fromArray(accounts, {
|
||||
itemId: 'id',
|
||||
parentItemId: 'parentAccountId',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return AccountSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search roles.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ condition: 'or', fieldKey: 'name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import moment from 'moment';
|
||||
import { isEmpty, castArray } from 'lodash';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||
|
||||
export default class AccountTransaction extends TenantModel {
|
||||
referenceType: string;
|
||||
credit: number;
|
||||
debit: number;
|
||||
exchangeRate: number;
|
||||
taxRate: number;
|
||||
transactionType: string;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'accounts_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['referenceTypeFormatted', 'creditLocal', 'debitLocal'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the credit amount in base currency.
|
||||
* @return {number}
|
||||
*/
|
||||
get creditLocal() {
|
||||
return this.credit * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the debit amount in base currency.
|
||||
* @return {number}
|
||||
*/
|
||||
get debitLocal() {
|
||||
return this.debit * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve formatted reference type.
|
||||
* @return {string}
|
||||
*/
|
||||
get referenceTypeFormatted() {
|
||||
return getTransactionTypeLabel(this.referenceType, this.transactionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters accounts by the given ids.
|
||||
* @param {Query} query
|
||||
* @param {number[]} accountsIds
|
||||
*/
|
||||
filterAccounts(query, accountsIds) {
|
||||
if (Array.isArray(accountsIds) && accountsIds.length > 0) {
|
||||
query.whereIn('account_id', accountsIds);
|
||||
}
|
||||
},
|
||||
filterTransactionTypes(query, types) {
|
||||
if (Array.isArray(types) && types.length > 0) {
|
||||
query.whereIn('reference_type', types);
|
||||
} else if (typeof types === 'string') {
|
||||
query.where('reference_type', types);
|
||||
}
|
||||
},
|
||||
filterDateRange(query, startDate, endDate, type = 'day') {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const fromDate = moment(startDate).startOf(type).format(dateFormat);
|
||||
const toDate = moment(endDate).endOf(type).format(dateFormat);
|
||||
|
||||
if (startDate) {
|
||||
query.where('date', '>=', fromDate);
|
||||
}
|
||||
if (endDate) {
|
||||
query.where('date', '<=', toDate);
|
||||
}
|
||||
},
|
||||
filterAmountRange(query, fromAmount, toAmount) {
|
||||
if (fromAmount) {
|
||||
query.andWhere((q) => {
|
||||
q.where('credit', '>=', fromAmount);
|
||||
q.orWhere('debit', '>=', fromAmount);
|
||||
});
|
||||
}
|
||||
if (toAmount) {
|
||||
query.andWhere((q) => {
|
||||
q.where('credit', '<=', toAmount);
|
||||
q.orWhere('debit', '<=', toAmount);
|
||||
});
|
||||
}
|
||||
},
|
||||
sumationCreditDebit(query) {
|
||||
query.select(['accountId']);
|
||||
|
||||
query.sum('credit as credit');
|
||||
query.sum('debit as debit');
|
||||
query.groupBy('account_id');
|
||||
},
|
||||
filterContactType(query, contactType) {
|
||||
query.where('contact_type', contactType);
|
||||
},
|
||||
filterContactIds(query, contactIds) {
|
||||
query.whereIn('contact_id', contactIds);
|
||||
},
|
||||
openingBalance(query, fromDate) {
|
||||
query.modify('filterDateRange', null, fromDate);
|
||||
query.modify('sumationCreditDebit');
|
||||
},
|
||||
closingBalance(query, toDate) {
|
||||
query.modify('filterDateRange', null, toDate);
|
||||
query.modify('sumationCreditDebit');
|
||||
},
|
||||
contactsOpeningBalance(
|
||||
query,
|
||||
openingDate,
|
||||
receivableAccounts,
|
||||
customersIds
|
||||
) {
|
||||
// Filter by date.
|
||||
query.modify('filterDateRange', null, openingDate);
|
||||
|
||||
// Filter by customers.
|
||||
query.whereNot('contactId', null);
|
||||
query.whereIn('accountId', castArray(receivableAccounts));
|
||||
|
||||
if (!isEmpty(customersIds)) {
|
||||
query.whereIn('contactId', castArray(customersIds));
|
||||
}
|
||||
// Group by the contact transactions.
|
||||
query.groupBy('contactId');
|
||||
query.sum('credit as credit');
|
||||
query.sum('debit as debit');
|
||||
query.select('contactId');
|
||||
},
|
||||
creditDebitSummation(query) {
|
||||
query.sum('credit as credit');
|
||||
query.sum('debit as debit');
|
||||
},
|
||||
groupByDateFormat(query, groupType = 'month') {
|
||||
const groupBy = {
|
||||
day: '%Y-%m-%d',
|
||||
month: '%Y-%m',
|
||||
year: '%Y',
|
||||
};
|
||||
const dateFormat = groupBy[groupType];
|
||||
|
||||
query.select(raw(`DATE_FORMAT(DATE, '${dateFormat}')`).as('date'));
|
||||
query.groupByRaw(`DATE_FORMAT(DATE, '${dateFormat}')`);
|
||||
},
|
||||
|
||||
filterByBranches(query, branchesIds) {
|
||||
const formattedBranchesIds = castArray(branchesIds);
|
||||
|
||||
query.whereIn('branchId', formattedBranchesIds);
|
||||
},
|
||||
|
||||
filterByProjects(query, projectsIds) {
|
||||
const formattedProjectsIds = castArray(projectsIds);
|
||||
|
||||
query.whereIn('projectId', formattedProjectsIds);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Account = require('models/Account');
|
||||
const Contact = require('models/Contact');
|
||||
|
||||
return {
|
||||
account: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'accounts_transactions.accountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
contact: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact.default,
|
||||
join: {
|
||||
from: 'accounts_transactions.contactId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
|
||||
export default class Auth {
|
||||
/**
|
||||
* Retrieve the authenticated user.
|
||||
*/
|
||||
static get user() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authenticated user.
|
||||
* @param {User} user
|
||||
*/
|
||||
static setAuthenticatedUser(user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the authenticated user ID.
|
||||
*/
|
||||
static userId() {
|
||||
if (!this.user) {
|
||||
return false;
|
||||
}
|
||||
return this.user.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user is logged or not.
|
||||
*/
|
||||
static isLogged() {
|
||||
return !!this.user;
|
||||
}
|
||||
|
||||
static loggedOut() {
|
||||
this.user = null;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { Model } from 'objection';
|
||||
|
||||
export class BankRule extends TenantModel {
|
||||
id!: number;
|
||||
name!: string;
|
||||
order!: number;
|
||||
applyIfAccountId!: number;
|
||||
applyIfTransactionType!: string;
|
||||
assignCategory!: string;
|
||||
assignAccountId!: number;
|
||||
assignPayee!: string;
|
||||
assignMemo!: string;
|
||||
conditionsType!: string;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'bank_rules';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { BankRuleCondition } = require('models/BankRuleCondition');
|
||||
const Account = require('models/Account');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Sale invoice associated entries.
|
||||
*/
|
||||
conditions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: BankRuleCondition,
|
||||
join: {
|
||||
from: 'bank_rules.id',
|
||||
to: 'bank_rule_conditions.ruleId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bank rule may associated to the assign account.
|
||||
*/
|
||||
assignAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'bank_rules.assignAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export class BankRuleCondition extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'bank_rule_conditions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
import { Features } from '@/interfaces';
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'vendor',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'bill_date',
|
||||
},
|
||||
importable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
exportable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'billNumber',
|
||||
print: {
|
||||
pageTitle: 'Bills',
|
||||
},
|
||||
fields: {
|
||||
vendor: {
|
||||
name: 'bill.field.vendor',
|
||||
column: 'vendor_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'vendor',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
bill_number: {
|
||||
name: 'bill.field.bill_number',
|
||||
column: 'bill_number',
|
||||
columnable: true,
|
||||
fieldType: 'text',
|
||||
},
|
||||
bill_date: {
|
||||
name: 'bill.field.bill_date',
|
||||
column: 'bill_date',
|
||||
columnable: true,
|
||||
fieldType: 'date',
|
||||
},
|
||||
due_date: {
|
||||
name: 'bill.field.due_date',
|
||||
column: 'due_date',
|
||||
columnable: true,
|
||||
fieldType: 'date',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'bill.field.reference_no',
|
||||
column: 'reference_no',
|
||||
columnable: true,
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
name: 'bill.field.status',
|
||||
fieldType: 'enumeration',
|
||||
columnable: true,
|
||||
options: [
|
||||
{ label: 'bill.field.status.paid', key: 'paid' },
|
||||
{ label: 'bill.field.status.partially-paid', key: 'partially-paid' },
|
||||
{ label: 'bill.field.status.overdue', key: 'overdue' },
|
||||
{ label: 'bill.field.status.unpaid', key: 'unpaid' },
|
||||
{ label: 'bill.field.status.opened', key: 'opened' },
|
||||
{ label: 'bill.field.status.draft', key: 'draft' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
amount: {
|
||||
name: 'bill.field.amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
payment_amount: {
|
||||
name: 'bill.field.payment_amount',
|
||||
column: 'payment_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
note: {
|
||||
name: 'bill.field.note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'bill.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
billDate: {
|
||||
name: 'Date',
|
||||
accessor: 'formattedBillDate',
|
||||
},
|
||||
billNumber: {
|
||||
name: 'Bill No.',
|
||||
type: 'text',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'Reference No.',
|
||||
type: 'text',
|
||||
},
|
||||
dueDate: {
|
||||
name: 'Due Date',
|
||||
type: 'date',
|
||||
accessor: 'formattedDueDate',
|
||||
},
|
||||
vendorId: {
|
||||
name: 'Vendor',
|
||||
accessor: 'vendor.displayName',
|
||||
type: 'text',
|
||||
},
|
||||
amount: {
|
||||
name: 'Amount',
|
||||
accessor: 'formattedAmount',
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'Exchange Rate',
|
||||
type: 'number',
|
||||
printable: false,
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'Currency Code',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
dueAmount: {
|
||||
name: 'Due Amount',
|
||||
accessor: 'formattedDueAmount',
|
||||
},
|
||||
paidAmount: {
|
||||
name: 'Paid Amount',
|
||||
accessor: 'formattedPaymentAmount',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
open: {
|
||||
name: 'Open',
|
||||
type: 'boolean',
|
||||
printable: false,
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
accessor: 'entries',
|
||||
type: 'collection',
|
||||
collectionOf: 'object',
|
||||
columns: {
|
||||
itemName: {
|
||||
name: 'Item Name',
|
||||
accessor: 'item.name',
|
||||
},
|
||||
rate: {
|
||||
name: 'Item Rate',
|
||||
accessor: 'rateFormatted',
|
||||
},
|
||||
quantity: {
|
||||
name: 'Item Quantity',
|
||||
accessor: 'quantityFormatted',
|
||||
},
|
||||
description: {
|
||||
name: 'Item Description',
|
||||
},
|
||||
amount: {
|
||||
name: 'Item Amount',
|
||||
accessor: 'totalFormatted',
|
||||
},
|
||||
},
|
||||
},
|
||||
branch: {
|
||||
name: 'Branch',
|
||||
type: 'text',
|
||||
accessor: 'branch.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
warehouse: {
|
||||
name: 'Warehouse',
|
||||
type: 'text',
|
||||
accessor: 'warehouse.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
billNumber: {
|
||||
name: 'Bill No.',
|
||||
fieldType: 'text',
|
||||
required: true,
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'Reference No.',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billDate: {
|
||||
name: 'Date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
dueDate: {
|
||||
name: 'Due Date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
vendorId: {
|
||||
name: 'Vendor',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Contact',
|
||||
relationImportMatch: 'displayName',
|
||||
required: true,
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'Exchange Rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
open: {
|
||||
name: 'Open',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
fieldType: 'collection',
|
||||
collectionOf: 'object',
|
||||
collectionMinLength: 1,
|
||||
required: true,
|
||||
fields: {
|
||||
itemId: {
|
||||
name: 'Item',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'Rate',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
quantity: {
|
||||
name: 'Quantity',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
name: 'Line Description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
branchId: {
|
||||
name: 'Branch',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Branch',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.BRANCHES],
|
||||
required: true,
|
||||
},
|
||||
warehouseId: {
|
||||
name: 'Warehouse',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Warehouse',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.WAREHOUSES],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Status field filter custom query.
|
||||
*/
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('statusFilter', role.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status field sort custom query.
|
||||
*/
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,625 +0,0 @@
|
||||
import { Model, raw, mixin } from 'objection';
|
||||
import { castArray, defaultTo, difference } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import BillSettings from './Bill.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Purchases/Bills/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
|
||||
export default class Bill extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public paymentAmount: number;
|
||||
public landedCostAmount: number;
|
||||
public allocatedCostAmount: number;
|
||||
public isInclusiveTax: boolean;
|
||||
public taxAmountWithheld: number;
|
||||
public exchangeRate: number;
|
||||
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
|
||||
public adjustment: number;
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'balance',
|
||||
'dueAmount',
|
||||
'isOpen',
|
||||
'isPartiallyPaid',
|
||||
'isFullyPaid',
|
||||
'isPaid',
|
||||
'remainingDays',
|
||||
'overdueDays',
|
||||
'isOverdue',
|
||||
'unallocatedCostAmount',
|
||||
'localAmount',
|
||||
'localAllocatedCostAmount',
|
||||
'billableAmount',
|
||||
'amountLocal',
|
||||
|
||||
'discountAmount',
|
||||
'discountAmountLocal',
|
||||
'discountPercentage',
|
||||
|
||||
'adjustmentLocal',
|
||||
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
'subtotalExludingTax',
|
||||
'taxAmountWithheldLocal',
|
||||
'total',
|
||||
'totalLocal',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice amount in base currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get amountLocal() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal. (Tax inclusive) if the tax inclusive is enabled.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal in base currency. (Tax inclusive) if the tax inclusive is enabled.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.amountLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sale invoice amount excluding tax.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalExcludingTax() {
|
||||
return this.isInclusiveTax
|
||||
? this.subtotal - this.taxAmountWithheld
|
||||
: this.subtotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tax amount withheld in base currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get taxAmountWithheldLocal() {
|
||||
return this.taxAmountWithheld * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountAmountLocal() {
|
||||
return this.discountAmount ? this.discountAmount * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage ? this.discount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjustment amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get adjustmentLocal() {
|
||||
return this.adjustment ? this.adjustment * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice total. (Tax included)
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
const adjustmentAmount = defaultTo(this.adjustment, 0);
|
||||
|
||||
return R.compose(
|
||||
R.add(adjustmentAmount),
|
||||
R.subtract(R.__, this.discountAmount),
|
||||
R.when(R.always(this.isInclusiveTax), R.add(this.taxAmountWithheld))
|
||||
)(this.subtotal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice total in local currency. (Tax included)
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'bills';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters the bills in draft status.
|
||||
*/
|
||||
draft(query) {
|
||||
query.where('opened_at', null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the opened bills.
|
||||
*/
|
||||
published(query) {
|
||||
query.whereNot('openedAt', null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the opened bills.
|
||||
*/
|
||||
opened(query) {
|
||||
query.whereNot('opened_at', null);
|
||||
},
|
||||
/**
|
||||
* Filters the unpaid bills.
|
||||
*/
|
||||
unpaid(query) {
|
||||
query.where('payment_amount', 0);
|
||||
},
|
||||
/**
|
||||
* Filters the due bills.
|
||||
*/
|
||||
dueBills(query) {
|
||||
query.where(
|
||||
raw(`COALESCE(AMOUNT, 0) -
|
||||
COALESCE(PAYMENT_AMOUNT, 0) -
|
||||
COALESCE(CREDITED_AMOUNT, 0) > 0
|
||||
`)
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Filters the overdue bills.
|
||||
*/
|
||||
overdue(query) {
|
||||
query.where('due_date', '<', moment().format('YYYY-MM-DD'));
|
||||
},
|
||||
/**
|
||||
* Filters the not overdue invoices.
|
||||
*/
|
||||
notOverdue(query, asDate = moment().format('YYYY-MM-DD')) {
|
||||
query.where('due_date', '>=', asDate);
|
||||
},
|
||||
/**
|
||||
* Filters the partially paid bills.
|
||||
*/
|
||||
partiallyPaid(query) {
|
||||
query.whereNot('payment_amount', 0);
|
||||
query.whereNot(raw('`PAYMENT_AMOUNT` = `AMOUNT`'));
|
||||
},
|
||||
/**
|
||||
* Filters the paid bills.
|
||||
*/
|
||||
paid(query) {
|
||||
query.where(raw('`PAYMENT_AMOUNT` = `AMOUNT`'));
|
||||
},
|
||||
/**
|
||||
* Filters the bills from the given date.
|
||||
*/
|
||||
fromDate(query, fromDate) {
|
||||
query.where('bill_date', '<=', fromDate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort the bills by full-payment bills.
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(`PAYMENT_AMOUNT = AMOUNT ${order}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Status filter.
|
||||
*/
|
||||
statusFilter(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'delivered':
|
||||
query.modify('delivered');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
default:
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters by branches.
|
||||
*/
|
||||
filterByBranches(query, branchesIds) {
|
||||
const formattedBranchesIds = castArray(branchesIds);
|
||||
|
||||
query.whereIn('branchId', formattedBranchesIds);
|
||||
},
|
||||
|
||||
dueBillsFromDate(query, asDate = moment().format('YYYY-MM-DD')) {
|
||||
query.modify('dueBills');
|
||||
query.modify('notOverdue');
|
||||
query.modify('fromDate', asDate);
|
||||
},
|
||||
|
||||
overdueBillsFromDate(query, asDate = moment().format('YYYY-MM-DD')) {
|
||||
query.modify('dueBills');
|
||||
query.modify('overdue', asDate);
|
||||
query.modify('fromDate', asDate);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
billable(query) {
|
||||
query.where(raw('AMOUNT > INVOICED_AMOUNT'));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice amount in organization base currency.
|
||||
* @deprecated
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amountLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local allocated cost amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAllocatedCostAmount() {
|
||||
return this.allocatedCostAmount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local landed cost amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localLandedCostAmount() {
|
||||
return this.landedCostAmount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local unallocated cost amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localUnallocatedCostAmount() {
|
||||
return this.unallocatedCostAmount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the balance of bill.
|
||||
* @return {number}
|
||||
*/
|
||||
get balance() {
|
||||
return this.paymentAmount + this.creditedAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Due amount of the given.
|
||||
* @return {number}
|
||||
*/
|
||||
get dueAmount() {
|
||||
return Math.max(this.total - this.balance, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmine whether the bill is open.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isOpen() {
|
||||
return !!this.openedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deetarmine whether the bill paid partially.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPartiallyPaid() {
|
||||
return this.dueAmount !== this.total && this.dueAmount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deetarmine whether the bill paid fully.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isFullyPaid() {
|
||||
return this.dueAmount === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the bill paid fully or partially.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPaid() {
|
||||
return this.isPartiallyPaid || this.isFullyPaid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the remaining days in number
|
||||
* @return {number|null}
|
||||
*/
|
||||
get remainingDays() {
|
||||
const currentMoment = moment();
|
||||
const dueDateMoment = moment(this.dueDate);
|
||||
|
||||
return Math.max(dueDateMoment.diff(currentMoment, 'days'), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the overdue days in number.
|
||||
* @return {number|null}
|
||||
*/
|
||||
get overdueDays() {
|
||||
const currentMoment = moment();
|
||||
const dueDateMoment = moment(this.dueDate);
|
||||
|
||||
return Math.max(currentMoment.diff(dueDateMoment, 'days'), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the due date is over.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isOverdue() {
|
||||
return this.overdueDays > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unallocated cost amount.
|
||||
* @return {number}
|
||||
*/
|
||||
get unallocatedCostAmount() {
|
||||
return Math.max(this.landedCostAmount - this.allocatedCostAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the calculated amount which have not been invoiced.
|
||||
*/
|
||||
get billableAmount() {
|
||||
return Math.max(this.total - this.invoicedAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bill model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return BillSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Vendor = require('models/Vendor');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const BillLandedCost = require('models/BillLandedCost');
|
||||
const Branch = require('models/Branch');
|
||||
const Warehouse = require('models/Warehouse');
|
||||
const TaxRateTransaction = require('models/TaxRateTransaction');
|
||||
const Document = require('models/Document');
|
||||
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
||||
|
||||
return {
|
||||
vendor: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Vendor.default,
|
||||
join: {
|
||||
from: 'bills.vendorId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'vendor');
|
||||
},
|
||||
},
|
||||
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'bills.id',
|
||||
to: 'items_entries.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'Bill');
|
||||
builder.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
locatedLandedCosts: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: BillLandedCost.default,
|
||||
join: {
|
||||
from: 'bills.id',
|
||||
to: 'bill_located_costs.billId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill may belongs to associated branch.
|
||||
*/
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch.default,
|
||||
join: {
|
||||
from: 'bills.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill may has associated warehouse.
|
||||
*/
|
||||
warehouse: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Warehouse.default,
|
||||
join: {
|
||||
from: 'bills.warehouseId',
|
||||
to: 'warehouses.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill may has associated tax rate transactions.
|
||||
*/
|
||||
taxes: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: TaxRateTransaction.default,
|
||||
join: {
|
||||
from: 'bills.id',
|
||||
to: 'tax_rate_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'Bill');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'bills.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'Bill');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill may belongs to matched bank transaction.
|
||||
*/
|
||||
matchedBankTransaction: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: MatchedBankTransaction,
|
||||
join: {
|
||||
from: 'bills.id',
|
||||
to: 'matched_bank_transactions.referenceId',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('reference_type', 'Bill');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the not found bills ids as array that associated to the given vendor.
|
||||
* @param {Array} billsIds
|
||||
* @param {number} vendorId -
|
||||
* @return {Array}
|
||||
*/
|
||||
static async getNotFoundBills(billsIds, vendorId) {
|
||||
const storedBills = await this.query().onBuild((builder) => {
|
||||
builder.whereIn('id', billsIds);
|
||||
|
||||
if (vendorId) {
|
||||
builder.where('vendor_id', vendorId);
|
||||
}
|
||||
});
|
||||
|
||||
const storedBillsIds = storedBills.map((t) => t.id);
|
||||
|
||||
const notFoundBillsIds = difference(billsIds, storedBillsIds);
|
||||
return notFoundBillsIds;
|
||||
}
|
||||
|
||||
static changePaymentAmount(billId, amount, trx) {
|
||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||
return this.query(trx)
|
||||
.where('id', billId)
|
||||
[changeMethod]('payment_amount', Math.abs(amount));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'bill_number', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import { lowerCase } from 'lodash';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class BillLandedCost extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'bill_located_costs';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['localAmount', 'allocationMethodFormatted'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the cost local amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocation method formatted.
|
||||
*/
|
||||
get allocationMethodFormatted() {
|
||||
const allocationMethod = lowerCase(this.allocationMethod);
|
||||
|
||||
const keyLabelsPairs = {
|
||||
value: 'allocation_method.value.label',
|
||||
quantity: 'allocation_method.quantity.label',
|
||||
};
|
||||
return keyLabelsPairs[allocationMethod] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const BillLandedCostEntry = require('models/BillLandedCostEntry');
|
||||
const Bill = require('models/Bill');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const ExpenseCategory = require('models/ExpenseCategory');
|
||||
|
||||
return {
|
||||
bill: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Bill.default,
|
||||
join: {
|
||||
from: 'bill_located_costs.billId',
|
||||
to: 'bills.id',
|
||||
},
|
||||
},
|
||||
allocateEntries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: BillLandedCostEntry.default,
|
||||
join: {
|
||||
from: 'bill_located_costs.id',
|
||||
to: 'bill_located_cost_entries.billLocatedCostId',
|
||||
},
|
||||
},
|
||||
allocatedFromBillEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'bill_located_costs.fromTransactionEntryId',
|
||||
to: 'items_entries.id',
|
||||
},
|
||||
},
|
||||
allocatedFromExpenseEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ExpenseCategory.default,
|
||||
join: {
|
||||
from: 'bill_located_costs.fromTransactionEntryId',
|
||||
to: 'expense_transaction_categories.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class BillLandedCostEntry extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'bill_located_cost_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
|
||||
return {
|
||||
itemEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'bill_located_cost_entries.entryId',
|
||||
to: 'items_entries.id',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'Bill');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
import { Features } from '@/interfaces';
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'vendor',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'bill_date',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'paymentNumber',
|
||||
fields: {
|
||||
vendor: {
|
||||
name: 'bill_payment.field.vendor',
|
||||
column: 'vendor_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'vendor',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
amount: {
|
||||
name: 'bill_payment.field.amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
due_amount: {
|
||||
name: 'bill_payment.field.due_amount',
|
||||
column: 'due_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
payment_account: {
|
||||
name: 'bill_payment.field.payment_account',
|
||||
column: 'payment_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'paymentAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
payment_number: {
|
||||
name: 'bill_payment.field.payment_number',
|
||||
column: 'payment_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
payment_date: {
|
||||
name: 'bill_payment.field.payment_date',
|
||||
column: 'payment_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'bill_payment.field.reference_no',
|
||||
column: 'reference',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'bill_payment.field.description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'bill_payment.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
vendor: {
|
||||
name: 'bill_payment.field.vendor',
|
||||
type: 'relation',
|
||||
accessor: 'vendor.displayName',
|
||||
},
|
||||
paymentDate: {
|
||||
name: 'bill_payment.field.payment_date',
|
||||
type: 'date',
|
||||
accessor: 'formattedPaymentDate',
|
||||
},
|
||||
paymentNumber: {
|
||||
name: 'bill_payment.field.payment_number',
|
||||
type: 'text',
|
||||
},
|
||||
paymentAccount: {
|
||||
name: 'bill_payment.field.payment_account',
|
||||
accessor: 'paymentAccount.name',
|
||||
type: 'text',
|
||||
},
|
||||
amount: {
|
||||
name: 'Amount',
|
||||
accessor: 'formattedAmount',
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'Currency Code',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'bill_payment.field.exchange_rate',
|
||||
type: 'number',
|
||||
printable: false,
|
||||
},
|
||||
statement: {
|
||||
name: 'bill_payment.field.note',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
reference: {
|
||||
name: 'bill_payment.field.reference',
|
||||
type: 'text',
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
accessor: 'entries',
|
||||
type: 'collection',
|
||||
collectionOf: 'object',
|
||||
columns: {
|
||||
date: {
|
||||
name: 'Bill date',
|
||||
accessor: 'bill.formattedBillDate',
|
||||
},
|
||||
billNo: {
|
||||
name: 'Bill No.',
|
||||
accessor: 'bill.billNo',
|
||||
},
|
||||
billRefNo: {
|
||||
name: 'Bill Reference No.',
|
||||
accessor: 'bill.referenceNo',
|
||||
},
|
||||
billAmount: {
|
||||
name: 'Bill Amount',
|
||||
accessor: 'bill.totalFormatted',
|
||||
},
|
||||
paidAmount: {
|
||||
name: 'Paid Amount',
|
||||
accessor: 'paymentAmountFormatted',
|
||||
},
|
||||
},
|
||||
},
|
||||
branch: {
|
||||
name: 'Branch',
|
||||
type: 'text',
|
||||
accessor: 'branch.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
vendorId: {
|
||||
name: 'bill_payment.field.vendor',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Contact',
|
||||
relationImportMatch: ['displayName'],
|
||||
required: true,
|
||||
},
|
||||
payment_date: {
|
||||
name: 'bill_payment.field.payment_date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
paymentNumber: {
|
||||
name: 'bill_payment.field.payment_number',
|
||||
fieldType: 'text',
|
||||
unique: true,
|
||||
importHint: 'The payment number should be unique.',
|
||||
},
|
||||
paymentAccountId: {
|
||||
name: 'bill_payment.field.payment_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'bill_payment.field.exchange_rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
statement: {
|
||||
name: 'bill_payment.field.note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference: {
|
||||
name: 'bill_payment.field.reference',
|
||||
fieldType: 'text',
|
||||
},
|
||||
entries: {
|
||||
name: 'bill_payment.field.entries',
|
||||
column: 'entries',
|
||||
fieldType: 'collection',
|
||||
collectionOf: 'object',
|
||||
collectionMinLength: 1,
|
||||
required: true,
|
||||
fields: {
|
||||
billId: {
|
||||
name: 'bill_payment.field.entries.bill',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Bill',
|
||||
relationImportMatch: 'billNumber',
|
||||
required: true,
|
||||
importHint: 'Matches the bill number.',
|
||||
},
|
||||
paymentAmount: {
|
||||
name: 'bill_payment.field.entries.payment_amount',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
branchId: {
|
||||
name: 'Branch',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Branch',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.BRANCHES],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,170 +0,0 @@
|
||||
import ModelBase from './Model';
|
||||
import BillPaymentSettings from './BillPayment.Settings';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants';
|
||||
|
||||
export class BillPayment extends ModelBase {
|
||||
vendorId: number;
|
||||
amount: number;
|
||||
currencyCode: string;
|
||||
reference: string;
|
||||
paymentAccountId: number;
|
||||
paymentNumber: string;
|
||||
paymentDate: Date;
|
||||
exchangeRate: number | null;
|
||||
userId: number;
|
||||
statement: string;
|
||||
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'bills_payments';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['localAmount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Payment amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return BillPaymentSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const BillPaymentEntry = require('models/BillPaymentEntry');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const Vendor = require('models/Vendor');
|
||||
const Account = require('models/Account');
|
||||
const Branch = require('models/Branch');
|
||||
const Document = require('models/Document');
|
||||
|
||||
return {
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: BillPaymentEntry.default,
|
||||
join: {
|
||||
from: 'bills_payments.id',
|
||||
to: 'bills_payments_entries.billPaymentId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
vendor: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Vendor.default,
|
||||
join: {
|
||||
from: 'bills_payments.vendorId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'vendor');
|
||||
},
|
||||
},
|
||||
|
||||
paymentAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'bills_payments.paymentAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'bills_payments.id',
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'BillPayment');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill payment may belongs to branch.
|
||||
*/
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch.default,
|
||||
join: {
|
||||
from: 'bills_payments.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill payment may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'bills_payments.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'BillPayment');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'payment_number', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class BillPaymentEntry extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'bills_payments_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Bill = require('models/Bill');
|
||||
const BillPayment = require('models/BillPayment');
|
||||
|
||||
return {
|
||||
payment: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: BillPayment.default,
|
||||
join: {
|
||||
from: 'bills_payments_entries.billPaymentId',
|
||||
to: 'bills_payments.id',
|
||||
},
|
||||
},
|
||||
bill: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Bill.default,
|
||||
join: {
|
||||
from: 'bills_payments_entries.billId',
|
||||
to: 'bills.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export default {
|
||||
fields2: {
|
||||
name: {
|
||||
name: 'Name',
|
||||
fieldType: 'text',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
columns: {},
|
||||
fields: {}
|
||||
};
|
||||
@@ -1,181 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import BranchMetadata from './Branch.settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
|
||||
export default class Branch extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'branches';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters accounts by the given ids.
|
||||
* @param {Query} query
|
||||
* @param {number[]} accountsIds
|
||||
*/
|
||||
isPrimary(query) {
|
||||
query.where('primary', true);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
const SaleEstimate = require('models/SaleEstimate');
|
||||
const SaleReceipt = require('models/SaleReceipt');
|
||||
const Bill = require('models/Bill');
|
||||
const PaymentReceive = require('models/PaymentReceive');
|
||||
const PaymentMade = require('models/BillPayment');
|
||||
const VendorCredit = require('models/VendorCredit');
|
||||
const CreditNote = require('models/CreditNote');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const InventoryTransaction = require('models/InventoryTransaction');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Branch may belongs to associated sale invoices.
|
||||
*/
|
||||
invoices: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'branches.id',
|
||||
to: 'sales_invoices.branchId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Branch may belongs to associated sale estimates.
|
||||
*/
|
||||
estimates: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleEstimate.default,
|
||||
join: {
|
||||
from: 'branches.id',
|
||||
to: 'sales_estimates.branchId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Branch may belongs to associated sale receipts.
|
||||
*/
|
||||
receipts: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleReceipt.default,
|
||||
join: {
|
||||
from: 'branches.id',
|
||||
to: 'sales_receipts.branchId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Branch may belongs to associated payment receives.
|
||||
*/
|
||||
paymentReceives: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: PaymentReceive.default,
|
||||
join: {
|
||||
from: 'branches.id',
|
||||
to: 'payment_receives.branchId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Branch may belongs to associated bills.
|
||||
*/
|
||||
bills: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Bill.default,
|
||||
join: {
|
||||
from: 'branches.id',
|
||||
to: 'bills.branchId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Branch may belongs to associated payment mades.
|
||||
*/
|
||||
paymentMades: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: PaymentMade.default,
|
||||
join: {
|
||||
from: 'branches.id',
|
||||
to: 'bills_payments.branchId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Branch may belongs to associated credit notes.
|
||||
*/
|
||||
creditNotes: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: CreditNote.default,
|
||||
join: {
|
||||
from: 'branches.id',
|
||||
to: 'credit_notes.branchId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Branch may belongs to associated to vendor credits.
|
||||
*/
|
||||
vendorCredit: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: VendorCredit.default,
|
||||
join: {
|
||||
from: 'branches.id',
|
||||
to: 'vendor_credits.branchId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Branch may belongs to associated to accounts transactions.
|
||||
*/
|
||||
accountsTransactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'branches.id',
|
||||
to: 'accounts_transactions.branchId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Branch may belongs to associated to inventory transactions.
|
||||
*/
|
||||
inventoryTransactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: InventoryTransaction.default,
|
||||
join: {
|
||||
from: 'branches.id',
|
||||
to: 'inventory_transactions.branchId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return BranchMetadata;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
fields: {
|
||||
name: {
|
||||
name: 'account.field.name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'account.field.description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
slug: {
|
||||
name: 'account.field.slug',
|
||||
column: 'slug',
|
||||
fieldType: 'text',
|
||||
columnable: false,
|
||||
filterable: false,
|
||||
},
|
||||
code: {
|
||||
name: 'account.field.code',
|
||||
column: 'code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
active: {
|
||||
name: 'account.field.active',
|
||||
column: 'active',
|
||||
fieldType: 'boolean',
|
||||
filterable: false,
|
||||
},
|
||||
balance: {
|
||||
name: 'account.field.balance',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currency: {
|
||||
name: 'account.field.currency',
|
||||
column: 'currency_code',
|
||||
fieldType: 'text',
|
||||
filterable: false,
|
||||
},
|
||||
created_at: {
|
||||
name: 'account.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,132 +0,0 @@
|
||||
/* eslint-disable global-require */
|
||||
import { mixin, Model } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import AccountTypesUtils from '@/lib/AccountTypes';
|
||||
import CashflowAccountSettings from './CashflowAccount.Settings';
|
||||
import ModelSettings from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Accounts/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class CashflowAccount extends mixin(TenantModel, [
|
||||
ModelSettings,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'accounts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['accountTypeLabel'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account type label.
|
||||
*/
|
||||
get accountTypeLabel() {
|
||||
return AccountTypesUtils.getType(this.accountType, 'label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to mark model as resourceable to viewable and filterable.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('accounts.active', !active);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Account model may has many transactions.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'accounts_transactions.accountId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the given type equals the account type.
|
||||
* @param {string} accountType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAccountType(accountType) {
|
||||
const types = castArray(accountType);
|
||||
return types.indexOf(this.accountType) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmine whether the given parent type equals the account type.
|
||||
* @param {string} parentType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isParentType(parentType) {
|
||||
return AccountTypesUtils.isParentTypeEqualsKey(
|
||||
this.accountType,
|
||||
parentType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return CashflowAccountSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search roles.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ condition: 'or', fieldKey: 'name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
/* eslint-disable global-require */
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import {
|
||||
getCashflowAccountTransactionsTypes,
|
||||
getCashflowTransactionType,
|
||||
} from '@/services/Cashflow/utils';
|
||||
import { CASHFLOW_DIRECTION } from '@/services/Cashflow/constants';
|
||||
import { getCashflowTransactionFormattedType } from '@/utils/transactions-types';
|
||||
|
||||
export default class CashflowTransaction extends TenantModel {
|
||||
transactionType: string;
|
||||
amount: number;
|
||||
exchangeRate: number;
|
||||
uncategorize: boolean;
|
||||
uncategorizedTransaction!: boolean;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'cashflow_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'localAmount',
|
||||
'transactionTypeFormatted',
|
||||
'isPublished',
|
||||
'typeMeta',
|
||||
'isCashCredit',
|
||||
'isCashDebit',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local amount of cashflow transaction.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the cashflow transaction is published.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPublished() {
|
||||
return !!this.publishedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction type formatted.
|
||||
* @returns {string}
|
||||
*/
|
||||
get transactionTypeFormatted() {
|
||||
return getCashflowTransactionFormattedType(this.transactionType);
|
||||
}
|
||||
|
||||
get typeMeta() {
|
||||
return getCashflowTransactionType(this.transactionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the cashflow transaction cash credit type.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isCashCredit() {
|
||||
return this.typeMeta?.direction === CASHFLOW_DIRECTION.OUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the cashflow transaction cash debit type.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isCashDebit() {
|
||||
return this.typeMeta?.direction === CASHFLOW_DIRECTION.IN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction imported from uncategorized transaction.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isCategroizedTranasction() {
|
||||
return !!this.uncategorizedTransaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filter the published transactions.
|
||||
*/
|
||||
published(query) {
|
||||
query.whereNot('published_at', null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter the not categorized transactions.
|
||||
*/
|
||||
notCategorized(query) {
|
||||
query.whereNull('cashflowTransactions.uncategorizedTransactionId');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter the categorized transactions.
|
||||
*/
|
||||
categorized(query) {
|
||||
query.whereNotNull('cashflowTransactions.uncategorizedTransactionId');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const CashflowTransactionLine = require('models/CashflowTransactionLine');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const Account = require('models/Account');
|
||||
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Cashflow transaction entries.
|
||||
*/
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: CashflowTransactionLine.default,
|
||||
join: {
|
||||
from: 'cashflow_transactions.id',
|
||||
to: 'cashflow_transaction_lines.cashflowTransactionId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Cashflow transaction has associated account transactions.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'cashflow_transactions.id',
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'CashflowTransaction');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Cashflow transaction may has associated cashflow account.
|
||||
*/
|
||||
cashflowAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'cashflow_transactions.cashflowAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Cashflow transcation may has associated to credit account.
|
||||
*/
|
||||
creditAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'cashflow_transactions.creditAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Cashflow transaction may belongs to matched bank transaction.
|
||||
*/
|
||||
matchedBankTransaction: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: MatchedBankTransaction,
|
||||
join: {
|
||||
from: 'cashflow_transactions.id',
|
||||
to: 'matched_bank_transactions.referenceId',
|
||||
},
|
||||
filter: (query) => {
|
||||
const referenceTypes = getCashflowAccountTransactionsTypes();
|
||||
query.whereIn('reference_type', referenceTypes);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/* eslint-disable global-require */
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class CashflowTransactionLine extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'cashflow_transaction_lines';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Account = require('models/Account');
|
||||
|
||||
return {
|
||||
cashflowAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'cashflow_transaction_lines.cashflowAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
creditAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'cashflow_transaction_lines.creditAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class Contact extends TenantModel {
|
||||
email: string;
|
||||
displayName: string;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'contacts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defined virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['contactNormal', 'closingBalance', 'formattedContactService'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the contact normal by the given contact type.
|
||||
*/
|
||||
static getContactNormalByType(contactType) {
|
||||
const types = {
|
||||
vendor: 'credit',
|
||||
customer: 'debit',
|
||||
};
|
||||
return types[contactType];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the contact normal by the given contact service.
|
||||
* @param {string} contactService
|
||||
*/
|
||||
static getFormattedContactService(contactService) {
|
||||
const types = {
|
||||
customer: 'Customer',
|
||||
vendor: 'Vendor',
|
||||
};
|
||||
return types[contactService];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the contact normal.
|
||||
*/
|
||||
get contactNormal() {
|
||||
return Contact.getContactNormalByType(this.contactService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve formatted contact service.
|
||||
*/
|
||||
get formattedContactService() {
|
||||
return Contact.getFormattedContactService(this.contactService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closing balance attribute.
|
||||
*/
|
||||
get closingBalance() {
|
||||
return this.balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
filterContactIds(query, customerIds) {
|
||||
query.whereIn('id', customerIds);
|
||||
},
|
||||
|
||||
customer(query) {
|
||||
query.where('contact_service', 'customer');
|
||||
},
|
||||
|
||||
vendor(query) {
|
||||
query.where('contact_service', 'vendor');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const SaleEstimate = require('models/SaleEstimate');
|
||||
const SaleReceipt = require('models/SaleReceipt');
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
const PaymentReceive = require('models/PaymentReceive');
|
||||
const Bill = require('models/Bill');
|
||||
const BillPayment = require('models/BillPayment');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Contact may has many sales invoices.
|
||||
*/
|
||||
salesInvoices: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'sales_invoices.customerId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Contact may has many sales estimates.
|
||||
*/
|
||||
salesEstimates: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleEstimate.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'sales_estimates.customerId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Contact may has many sales receipts.
|
||||
*/
|
||||
salesReceipts: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleReceipt.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'sales_receipts.customerId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Contact may has many payments receives.
|
||||
*/
|
||||
paymentReceives: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: PaymentReceive.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'payment_receives.customerId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Contact may has many bills.
|
||||
*/
|
||||
bills: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Bill.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'bills.vendorId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Contact may has many bills payments.
|
||||
*/
|
||||
billPayments: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: BillPayment.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'bills_payments.vendorId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Contact may has many accounts transactions.
|
||||
*/
|
||||
accountsTransactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'accounts_transactions.contactId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
import { Features } from '@/interfaces';
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'creditNoteNumber',
|
||||
|
||||
print: {
|
||||
pageTitle: 'Credit Notes',
|
||||
},
|
||||
|
||||
fields: {
|
||||
customer: {
|
||||
name: 'credit_note.field.customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
credit_date: {
|
||||
name: 'credit_note.field.credit_note_date',
|
||||
column: 'credit_note_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
credit_number: {
|
||||
name: 'credit_note.field.credit_note_number',
|
||||
column: 'credit_note_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'credit_note.field.reference_no',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
amount: {
|
||||
name: 'credit_note.field.amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currency_code: {
|
||||
name: 'credit_note.field.currency_code',
|
||||
column: 'currency_code',
|
||||
fieldType: 'number',
|
||||
},
|
||||
note: {
|
||||
name: 'credit_note.field.note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
terms_conditions: {
|
||||
name: 'credit_note.field.terms_conditions',
|
||||
column: 'terms_conditions',
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
name: 'credit_note.field.status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', label: 'credit_note.field.status.draft' },
|
||||
{ key: 'published', label: 'credit_note.field.status.published' },
|
||||
{ key: 'open', label: 'credit_note.field.status.open' },
|
||||
{ key: 'closed', label: 'credit_note.field.status.closed' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
created_at: {
|
||||
name: 'credit_note.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
customer: {
|
||||
name: 'Customer',
|
||||
accessor: 'customer.displayName',
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'Exchange Rate',
|
||||
printable: false,
|
||||
},
|
||||
creditNoteDate: {
|
||||
name: 'Credit Note Date',
|
||||
accessor: 'formattedCreditNoteDate',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'Reference No.',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
},
|
||||
termsConditions: {
|
||||
name: 'Terms & Conditions',
|
||||
printable: false,
|
||||
},
|
||||
creditNoteNumber: {
|
||||
name: 'Credit Note Number',
|
||||
printable: false,
|
||||
},
|
||||
open: {
|
||||
name: 'Open',
|
||||
type: 'boolean',
|
||||
printable: false,
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
type: 'collection',
|
||||
collectionOf: 'object',
|
||||
columns: {
|
||||
itemName: {
|
||||
name: 'Item Name',
|
||||
accessor: 'item.name',
|
||||
},
|
||||
rate: {
|
||||
name: 'Item Rate',
|
||||
accessor: 'rateFormatted',
|
||||
},
|
||||
quantity: {
|
||||
name: 'Item Quantity',
|
||||
accessor: 'quantityFormatted',
|
||||
},
|
||||
description: {
|
||||
name: 'Item Description',
|
||||
},
|
||||
amount: {
|
||||
name: 'Item Amount',
|
||||
accessor: 'totalFormatted',
|
||||
},
|
||||
},
|
||||
},
|
||||
branch: {
|
||||
name: 'Branch',
|
||||
type: 'text',
|
||||
accessor: 'branch.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
warehouse: {
|
||||
name: 'Warehouse',
|
||||
type: 'text',
|
||||
accessor: 'warehouse.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
customerId: {
|
||||
name: 'Customer',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Contact',
|
||||
relationImportMatch: 'displayName',
|
||||
required: true,
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'Exchange Rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
creditNoteDate: {
|
||||
name: 'Credit Note Date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'Reference No.',
|
||||
fieldType: 'text',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
termsConditions: {
|
||||
name: 'Terms & Conditions',
|
||||
fieldType: 'text',
|
||||
},
|
||||
creditNoteNumber: {
|
||||
name: 'Credit Note Number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
open: {
|
||||
name: 'Open',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
fieldType: 'collection',
|
||||
collectionOf: 'object',
|
||||
collectionMinLength: 1,
|
||||
fields: {
|
||||
itemId: {
|
||||
name: 'Item',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'Rate',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
quantity: {
|
||||
name: 'Quantity',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
name: 'Description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
branchId: {
|
||||
name: 'Branch',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Branch',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.BRANCHES],
|
||||
required: true,
|
||||
},
|
||||
warehouseId: {
|
||||
name: 'Warehouse',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Warehouse',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.WAREHOUSES],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,411 +0,0 @@
|
||||
import { mixin, Model, raw } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/CreditNotes/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import CreditNoteMeta from './CreditNote.Meta';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
|
||||
export default class CreditNote extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public exchangeRate: number;
|
||||
public openedAt: Date;
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
public adjustment: number;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'credit_notes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'localAmount',
|
||||
'isDraft',
|
||||
'isPublished',
|
||||
'isOpen',
|
||||
'isClosed',
|
||||
|
||||
'creditsRemaining',
|
||||
'creditsUsed',
|
||||
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
|
||||
'discountAmount',
|
||||
'discountAmountLocal',
|
||||
'discountPercentage',
|
||||
|
||||
'total',
|
||||
'totalLocal',
|
||||
|
||||
'adjustmentLocal',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit note amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit note subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit note subtotal in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.subtotal * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmountLocal() {
|
||||
return this.discountAmount ? this.discountAmount * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage ? this.discount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjustment amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get adjustmentLocal() {
|
||||
return this.adjustment ? this.adjustment * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit note total.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.subtotal - this.discountAmount + this.adjustment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Credit note total in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the credit note is draft.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isDraft() {
|
||||
return !this.openedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether vendor credit is published.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isPublished() {
|
||||
return !!this.openedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the credit note is open.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isOpen() {
|
||||
return !!this.openedAt && this.creditsRemaining > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the credit note is closed.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isClosed() {
|
||||
return this.openedAt && this.creditsRemaining === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the credits remaining.
|
||||
*/
|
||||
get creditsRemaining() {
|
||||
return Math.max(this.amount - this.refundedAmount - this.invoicesAmount, 0);
|
||||
}
|
||||
|
||||
get creditsUsed() {
|
||||
return this.refundedAmount + this.invoicesAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters the credit notes in draft status.
|
||||
*/
|
||||
draft(query) {
|
||||
query.where('opened_at', null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the.
|
||||
*/
|
||||
published(query) {
|
||||
query.whereNot('opened_at', null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the open credit notes.
|
||||
*/
|
||||
open(query) {
|
||||
query
|
||||
.where(
|
||||
raw(`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICES_AMOUNT) <
|
||||
COALESCE(AMOUNT)`)
|
||||
)
|
||||
.modify('published');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the closed credit notes.
|
||||
*/
|
||||
closed(query) {
|
||||
query
|
||||
.where(
|
||||
raw(`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICES_AMOUNT) =
|
||||
COALESCE(AMOUNT)`)
|
||||
)
|
||||
.modify('published');
|
||||
},
|
||||
|
||||
/**
|
||||
* Status filter.
|
||||
*/
|
||||
filterByStatus(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'published':
|
||||
query.modify('published');
|
||||
break;
|
||||
case 'open':
|
||||
default:
|
||||
query.modify('open');
|
||||
break;
|
||||
case 'closed':
|
||||
query.modify('closed');
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(
|
||||
`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICES_AMOUNT) = COALESCE(AMOUNT) ${order}`
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const Customer = require('models/Customer');
|
||||
const Branch = require('models/Branch');
|
||||
const Document = require('models/Document');
|
||||
const Warehouse = require('models/Warehouse');
|
||||
const { PdfTemplate } = require('models/PdfTemplate');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Credit note associated entries.
|
||||
*/
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'credit_notes.id',
|
||||
to: 'items_entries.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'CreditNote');
|
||||
builder.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Belongs to customer model.
|
||||
*/
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Customer.default,
|
||||
join: {
|
||||
from: 'credit_notes.customerId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'Customer');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Credit note associated GL entries.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'credit_notes.id',
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'CreditNote');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Credit note may belongs to branch.
|
||||
*/
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch.default,
|
||||
join: {
|
||||
from: 'credit_notes.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Credit note may has associated warehouse.
|
||||
*/
|
||||
warehouse: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Warehouse.default,
|
||||
join: {
|
||||
from: 'credit_notes.warehouseId',
|
||||
to: 'warehouses.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Credit note may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'credit_notes.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'CreditNote');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Credit note may belongs to pdf branding template.
|
||||
*/
|
||||
pdfTemplate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PdfTemplate,
|
||||
join: {
|
||||
from: 'credit_notes.pdfTemplateId',
|
||||
to: 'pdf_templates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sale invoice meta.
|
||||
*/
|
||||
static get meta() {
|
||||
return CreditNoteMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model searchable.
|
||||
*/
|
||||
static get searchable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'credit_number', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { mixin, Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class CreditNoteAppliedInvoice extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'credit_note_applied_invoice';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
const CreditNote = require('models/CreditNote');
|
||||
|
||||
return {
|
||||
saleInvoice: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'credit_note_applied_invoice.invoiceId',
|
||||
to: 'sales_invoices.id',
|
||||
},
|
||||
},
|
||||
|
||||
creditNote: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: CreditNote.default,
|
||||
join: {
|
||||
from: 'credit_note_applied_invoice.creditNoteId',
|
||||
to: 'credit_notes.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class CreditNoteAppliedInvoiceEntry extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'credit_associated_transaction_entry';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class Currency extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'currencies';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
export default (Model) =>
|
||||
class CustomViewBaseModel extends Model {
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default view by the given slug.
|
||||
*/
|
||||
static getDefaultViewBySlug(viewSlug) {
|
||||
return this.defaultViews.find((view) => view.slug === viewSlug) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default views.
|
||||
* @returns {IView[]}
|
||||
*/
|
||||
static getDefaultViews() {
|
||||
return this.defaultViews;
|
||||
}
|
||||
};
|
||||
@@ -1,422 +0,0 @@
|
||||
export default {
|
||||
importable: true,
|
||||
exportable: true,
|
||||
defaultFilterField: 'displayName',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
print: {
|
||||
pageTitle: 'Customers',
|
||||
},
|
||||
fields: {
|
||||
first_name: {
|
||||
name: 'vendor.field.first_name',
|
||||
column: 'first_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
last_name: {
|
||||
name: 'vendor.field.last_name',
|
||||
column: 'last_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
display_name: {
|
||||
name: 'vendor.field.display_name',
|
||||
column: 'display_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
email: {
|
||||
name: 'vendor.field.email',
|
||||
column: 'email',
|
||||
fieldType: 'text',
|
||||
},
|
||||
work_phone: {
|
||||
name: 'vendor.field.work_phone',
|
||||
column: 'work_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
personal_phone: {
|
||||
name: 'vendor.field.personal_pone',
|
||||
column: 'personal_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
company_name: {
|
||||
name: 'vendor.field.company_name',
|
||||
column: 'company_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
website: {
|
||||
name: 'vendor.field.website',
|
||||
column: 'website',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'vendor.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
balance: {
|
||||
name: 'vendor.field.balance',
|
||||
column: 'balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
opening_balance: {
|
||||
name: 'vendor.field.opening_balance',
|
||||
column: 'opening_balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
opening_balance_at: {
|
||||
name: 'vendor.field.opening_balance_at',
|
||||
column: 'opening_balance_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
currency_code: {
|
||||
name: 'vendor.field.currency',
|
||||
column: 'currency_code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
name: 'vendor.field.status',
|
||||
type: 'enumeration',
|
||||
options: [
|
||||
{ key: 'overdue', label: 'vendor.field.status.overdue' },
|
||||
{ key: 'unpaid', label: 'vendor.field.status.unpaid' },
|
||||
],
|
||||
filterCustomQuery: (query, role) => {
|
||||
switch (role.value) {
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
customerType: {
|
||||
name: 'Customer Type',
|
||||
type: 'text',
|
||||
accessor: 'formattedCustomerType',
|
||||
},
|
||||
firstName: {
|
||||
name: 'vendor.field.first_name',
|
||||
type: 'text',
|
||||
},
|
||||
lastName: {
|
||||
name: 'vendor.field.last_name',
|
||||
type: 'text',
|
||||
},
|
||||
displayName: {
|
||||
name: 'vendor.field.display_name',
|
||||
type: 'text',
|
||||
},
|
||||
email: {
|
||||
name: 'vendor.field.email',
|
||||
type: 'text',
|
||||
},
|
||||
workPhone: {
|
||||
name: 'vendor.field.work_phone',
|
||||
type: 'text',
|
||||
},
|
||||
personalPhone: {
|
||||
name: 'vendor.field.personal_phone',
|
||||
type: 'text',
|
||||
},
|
||||
companyName: {
|
||||
name: 'vendor.field.company_name',
|
||||
type: 'text',
|
||||
},
|
||||
website: {
|
||||
name: 'vendor.field.website',
|
||||
type: 'text',
|
||||
},
|
||||
balance: {
|
||||
name: 'vendor.field.balance',
|
||||
type: 'number',
|
||||
accessor: 'formattedBalance',
|
||||
},
|
||||
openingBalance: {
|
||||
name: 'vendor.field.opening_balance',
|
||||
type: 'number',
|
||||
printable: false,
|
||||
},
|
||||
openingBalanceAt: {
|
||||
name: 'vendor.field.opening_balance_at',
|
||||
type: 'date',
|
||||
printable: false,
|
||||
accessor: 'formattedOpeningBalanceAt'
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'vendor.field.currency',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
status: {
|
||||
name: 'vendor.field.status',
|
||||
printable: false,
|
||||
},
|
||||
note: {
|
||||
name: 'vendor.field.note',
|
||||
printable: false,
|
||||
},
|
||||
// Billing Address
|
||||
billingAddress1: {
|
||||
name: 'Billing Address 1',
|
||||
column: 'billing_address1',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
billingAddress2: {
|
||||
name: 'Billing Address 2',
|
||||
column: 'billing_address2',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
billingAddressCity: {
|
||||
name: 'Billing Address City',
|
||||
column: 'billing_address_city',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
billingAddressCountry: {
|
||||
name: 'Billing Address Country',
|
||||
column: 'billing_address_country',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
billingAddressPostcode: {
|
||||
name: 'Billing Address Postcode',
|
||||
column: 'billing_address_postcode',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
billingAddressState: {
|
||||
name: 'Billing Address State',
|
||||
column: 'billing_address_state',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
billingAddressPhone: {
|
||||
name: 'Billing Address Phone',
|
||||
column: 'billing_address_phone',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
// Shipping Address
|
||||
shippingAddress1: {
|
||||
name: 'Shipping Address 1',
|
||||
column: 'shipping_address1',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
shippingAddress2: {
|
||||
name: 'Shipping Address 2',
|
||||
column: 'shipping_address2',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
shippingAddressCity: {
|
||||
name: 'Shipping Address City',
|
||||
column: 'shipping_address_city',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
shippingAddressCountry: {
|
||||
name: 'Shipping Address Country',
|
||||
column: 'shipping_address_country',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
shippingAddressPostcode: {
|
||||
name: 'Shipping Address Postcode',
|
||||
column: 'shipping_address_postcode',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
shippingAddressPhone: {
|
||||
name: 'Shipping Address Phone',
|
||||
column: 'shipping_address_phone',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
shippingAddressState: {
|
||||
name: 'Shipping Address State',
|
||||
column: 'shipping_address_state',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
createdAt: {
|
||||
name: 'vendor.field.created_at',
|
||||
type: 'date',
|
||||
printable: false,
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
customerType: {
|
||||
name: 'Customer Type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'business', label: 'Business' },
|
||||
{ key: 'individual', label: 'Individual' },
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
firstName: {
|
||||
name: 'customer.field.first_name',
|
||||
column: 'first_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
lastName: {
|
||||
name: 'customer.field.last_name',
|
||||
column: 'last_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
displayName: {
|
||||
name: 'customer.field.display_name',
|
||||
column: 'display_name',
|
||||
fieldType: 'text',
|
||||
required: true,
|
||||
},
|
||||
email: {
|
||||
name: 'customer.field.email',
|
||||
column: 'email',
|
||||
fieldType: 'text',
|
||||
},
|
||||
workPhone: {
|
||||
name: 'customer.field.work_phone',
|
||||
column: 'work_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
personalPhone: {
|
||||
name: 'customer.field.personal_phone',
|
||||
column: 'personal_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
companyName: {
|
||||
name: 'customer.field.company_name',
|
||||
column: 'company_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
website: {
|
||||
name: 'customer.field.website',
|
||||
column: 'website',
|
||||
fieldType: 'url',
|
||||
},
|
||||
openingBalance: {
|
||||
name: 'customer.field.opening_balance',
|
||||
column: 'opening_balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
openingBalanceAt: {
|
||||
name: 'customer.field.opening_balance_at',
|
||||
column: 'opening_balance_at',
|
||||
filterable: false,
|
||||
fieldType: 'date',
|
||||
},
|
||||
openingBalanceExchangeRate: {
|
||||
name: 'Opening Balance Ex. Rate',
|
||||
column: 'opening_balance_exchange_rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'customer.field.currency',
|
||||
column: 'currency_code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
active: {
|
||||
name: 'Active',
|
||||
column: 'active',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
// Billing Address
|
||||
billingAddress1: {
|
||||
name: 'Billing Address 1',
|
||||
column: 'billing_address1',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddress2: {
|
||||
name: 'Billing Address 2',
|
||||
column: 'billing_address2',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddressCity: {
|
||||
name: 'Billing Address City',
|
||||
column: 'billing_address_city',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddressCountry: {
|
||||
name: 'Billing Address Country',
|
||||
column: 'billing_address_country',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddressPostcode: {
|
||||
name: 'Billing Address Postcode',
|
||||
column: 'billing_address_postcode',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddressState: {
|
||||
name: 'Billing Address State',
|
||||
column: 'billing_address_state',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddressPhone: {
|
||||
name: 'Billing Address Phone',
|
||||
column: 'billing_address_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
// Shipping Address
|
||||
shippingAddress1: {
|
||||
name: 'Shipping Address 1',
|
||||
column: 'shipping_address1',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddress2: {
|
||||
name: 'Shipping Address 2',
|
||||
column: 'shipping_address2',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddressCity: {
|
||||
name: 'Shipping Address City',
|
||||
column: 'shipping_address_city',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddressCountry: {
|
||||
name: 'Shipping Address Country',
|
||||
column: 'shipping_address_country',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddressPostcode: {
|
||||
name: 'Shipping Address Postcode',
|
||||
column: 'shipping_address_postcode',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddressPhone: {
|
||||
name: 'Shipping Address Phone',
|
||||
column: 'shipping_address_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddressState: {
|
||||
name: 'Shipping Address State',
|
||||
column: 'shipping_address_state',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function statusFieldFilterQuery(query, role) {
|
||||
switch (role.value) {
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import PaginationQueryBuilder from './Pagination';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomerSettings from './Customer.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Contacts/Customers/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
class CustomerQueryBuilder extends PaginationQueryBuilder {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.onBuild((builder) => {
|
||||
if (builder.isFind() || builder.isDelete() || builder.isUpdate()) {
|
||||
builder.where('contact_service', 'customer');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default class Customer extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
email: string;
|
||||
displayName: string;
|
||||
|
||||
/**
|
||||
* Query builder.
|
||||
*/
|
||||
static get QueryBuilder() {
|
||||
return CustomerQueryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'contacts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defined virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['localOpeningBalance', 'closingBalance', 'contactNormal'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Closing balance attribute.
|
||||
*/
|
||||
get closingBalance() {
|
||||
return this.balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local opening balance.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localOpeningBalance() {
|
||||
return this.openingBalance
|
||||
? this.openingBalance * this.openingBalanceExchangeRate
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the contact noraml;
|
||||
*/
|
||||
get contactNormal() {
|
||||
return 'debit';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
get contactAddresses() {
|
||||
return [
|
||||
{
|
||||
mail: this.email,
|
||||
label: this.displayName,
|
||||
primary: true
|
||||
},
|
||||
].filter((c) => c.mail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('active', !active);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the active customers.
|
||||
*/
|
||||
active(query) {
|
||||
query.where('active', 1);
|
||||
},
|
||||
/**
|
||||
* Filters the inactive customers.
|
||||
*/
|
||||
inactive(query) {
|
||||
query.where('active', 0);
|
||||
},
|
||||
/**
|
||||
* Filters the customers that have overdue invoices.
|
||||
*/
|
||||
overdue(query) {
|
||||
query.select(
|
||||
'*',
|
||||
Customer.relatedQuery('overDueInvoices', query.knex())
|
||||
.count()
|
||||
.as('countOverdue')
|
||||
);
|
||||
query.having('countOverdue', '>', 0);
|
||||
},
|
||||
/**
|
||||
* Filters the unpaid customers.
|
||||
*/
|
||||
unpaid(query) {
|
||||
query.whereRaw('`BALANCE` + `OPENING_BALANCE` <> 0');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
|
||||
return {
|
||||
salesInvoices: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'sales_invoices.customerId',
|
||||
},
|
||||
},
|
||||
|
||||
overDueInvoices: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'sales_invoices.customerId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.modify('overdue');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get meta() {
|
||||
return CustomerSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'display_name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'first_name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'last_name', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'company_name', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'email', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'work_phone', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'personal_phone', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'website', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import moment from 'moment';
|
||||
|
||||
export default (Model) => {
|
||||
return class DateSession extends Model {
|
||||
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
$beforeUpdate(opt, context) {
|
||||
const maybePromise = super.$beforeUpdate(opt, context);
|
||||
|
||||
return Promise.resolve(maybePromise).then(() => {
|
||||
const key = this.timestamps[1];
|
||||
|
||||
if (key && !this[key]) {
|
||||
this[key] = moment().format('YYYY/MM/DD HH:mm:ss');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$beforeInsert(context) {
|
||||
const maybePromise = super.$beforeInsert(context);
|
||||
|
||||
return Promise.resolve(maybePromise).then(() => {
|
||||
const key = this.timestamps[0];
|
||||
|
||||
if (key && !this[key]) {
|
||||
this[key] = moment().format('YYYY/MM/DD HH:mm:ss');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class Document extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'documents';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class DocumentLink extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'document_links';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Document = require('models/Document');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Sale invoice associated entries.
|
||||
*/
|
||||
document: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'document_links.documentId',
|
||||
to: 'documents.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class ExchangeRate extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'exchange_rates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
*/
|
||||
static get fields(){
|
||||
return {
|
||||
currency_code: {
|
||||
label: 'Currency',
|
||||
column: 'currency_code'
|
||||
},
|
||||
exchange_rate: {
|
||||
label: 'Exchange rate',
|
||||
column: 'exchange_rate',
|
||||
},
|
||||
date: {
|
||||
label: 'Date',
|
||||
column: 'date',
|
||||
},
|
||||
created_at: {
|
||||
label: "Created at",
|
||||
column: "created_at",
|
||||
columnType: "date",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
import { Features } from '@/interfaces';
|
||||
|
||||
/**
|
||||
* Expense - Settings.
|
||||
*/
|
||||
export default {
|
||||
defaultFilterField: 'description',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
importable: true,
|
||||
exportFlattenOn: 'categories',
|
||||
exportable: true,
|
||||
print: {
|
||||
pageTitle: 'Expenses',
|
||||
},
|
||||
fields: {
|
||||
payment_date: {
|
||||
name: 'expense.field.payment_date',
|
||||
column: 'payment_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
payment_account: {
|
||||
name: 'expense.field.payment_account',
|
||||
column: 'payment_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'paymentAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
amount: {
|
||||
name: 'expense.field.amount',
|
||||
column: 'total_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'expense.field.reference_no',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'expense.field.description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
published: {
|
||||
name: 'expense.field.published',
|
||||
column: 'published_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
status: {
|
||||
name: 'expense.field.status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ label: 'expense.field.status.draft', key: 'draft' },
|
||||
{ label: 'expense.field.status.published', key: 'published' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
created_at: {
|
||||
name: 'expense.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
paymentReceive: {
|
||||
name: 'expense.field.payment_account',
|
||||
type: 'text',
|
||||
accessor: 'paymentAccount.name',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'expense.field.reference_no',
|
||||
type: 'text',
|
||||
},
|
||||
paymentDate: {
|
||||
name: 'expense.field.payment_date',
|
||||
accessor: 'formattedDate',
|
||||
type: 'date',
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'expense.field.currency_code',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'expense.field.exchange_rate',
|
||||
type: 'number',
|
||||
printable: false,
|
||||
},
|
||||
description: {
|
||||
name: 'expense.field.description',
|
||||
type: 'text',
|
||||
},
|
||||
categories: {
|
||||
name: 'expense.field.categories',
|
||||
type: 'collection',
|
||||
collectionOf: 'object',
|
||||
columns: {
|
||||
expenseAccount: {
|
||||
name: 'expense.field.expense_account',
|
||||
accessor: 'expenseAccount.name',
|
||||
},
|
||||
amount: {
|
||||
name: 'expense.field.amount',
|
||||
accessor: 'amountFormatted',
|
||||
},
|
||||
description: {
|
||||
name: 'expense.field.line_description',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
publish: {
|
||||
name: 'expense.field.publish',
|
||||
type: 'boolean',
|
||||
printable: false,
|
||||
},
|
||||
branch: {
|
||||
name: 'Branch',
|
||||
type: 'text',
|
||||
accessor: 'branch.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
paymentAccountId: {
|
||||
name: 'expense.field.payment_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'expense.field.reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
paymentDate: {
|
||||
name: 'expense.field.payment_date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'expense.field.currency_code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'expense.field.exchange_rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
description: {
|
||||
name: 'expense.field.description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
categories: {
|
||||
name: 'expense.field.categories',
|
||||
fieldType: 'collection',
|
||||
collectionOf: 'object',
|
||||
fields: {
|
||||
expenseAccountId: {
|
||||
name: 'expense.field.expense_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
amount: {
|
||||
name: 'expense.field.amount',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
name: 'expense.field.line_description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
publish: {
|
||||
name: 'expense.field.publish',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
branchId: {
|
||||
name: 'Branch',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Branch',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.BRANCHES],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
import { Model, mixin, raw } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { viewRolesBuilder } from '@/lib/ViewRolesBuilder';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ExpenseSettings from './Expense.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Expenses/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import moment from 'moment';
|
||||
|
||||
export default class Expense extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'expenses_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Account transaction reference type.
|
||||
*/
|
||||
static get referenceType() {
|
||||
return 'Expense';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'isPublished',
|
||||
'unallocatedCostAmount',
|
||||
'localAmount',
|
||||
'localLandedCostAmount',
|
||||
'localUnallocatedCostAmount',
|
||||
'localAllocatedCostAmount',
|
||||
'billableAmount',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local amount of expense.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.totalAmount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rertieves the local landed cost amount of expense.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localLandedCostAmount() {
|
||||
return this.landedCostAmount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local allocated cost amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAllocatedCostAmount() {
|
||||
return this.allocatedCostAmount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unallocated cost amount.
|
||||
* @return {number}
|
||||
*/
|
||||
get unallocatedCostAmount() {
|
||||
return Math.max(this.totalAmount - this.allocatedCostAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local unallocated cost amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localUnallocatedCostAmount() {
|
||||
return this.unallocatedCostAmount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the expense is published.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isPublished() {
|
||||
return Boolean(this.publishedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the calculated amount which have not been invoiced.
|
||||
*/
|
||||
get billableAmount() {
|
||||
return Math.max(this.totalAmount - this.invoicedAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
filterByDateRange(query, startDate, endDate) {
|
||||
if (startDate) {
|
||||
query.where('date', '>=', startDate);
|
||||
}
|
||||
if (endDate) {
|
||||
query.where('date', '<=', endDate);
|
||||
}
|
||||
},
|
||||
filterByAmountRange(query, from, to) {
|
||||
if (from) {
|
||||
query.where('amount', '>=', from);
|
||||
}
|
||||
if (to) {
|
||||
query.where('amount', '<=', to);
|
||||
}
|
||||
},
|
||||
filterByExpenseAccount(query, accountId) {
|
||||
if (accountId) {
|
||||
query.where('expense_account_id', accountId);
|
||||
}
|
||||
},
|
||||
filterByPaymentAccount(query, accountId) {
|
||||
if (accountId) {
|
||||
query.where('payment_account_id', accountId);
|
||||
}
|
||||
},
|
||||
viewRolesBuilder(query, conditionals, expression) {
|
||||
viewRolesBuilder(conditionals, expression)(query);
|
||||
},
|
||||
|
||||
filterByDraft(query) {
|
||||
query.where('published_at', null);
|
||||
},
|
||||
|
||||
filterByPublished(query) {
|
||||
query.whereNot('published_at', null);
|
||||
},
|
||||
|
||||
filterByStatus(query, status) {
|
||||
switch (status) {
|
||||
case 'draft':
|
||||
query.modify('filterByDraft');
|
||||
break;
|
||||
case 'published':
|
||||
default:
|
||||
query.modify('filterByPublished');
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
publish(query) {
|
||||
query.update({
|
||||
publishedAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the expenses have billable amount.
|
||||
*/
|
||||
billable(query) {
|
||||
query.where(raw('AMOUNT > INVOICED_AMOUNT'));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Account = require('models/Account');
|
||||
const ExpenseCategory = require('models/ExpenseCategory');
|
||||
const Document = require('models/Document');
|
||||
const Branch = require('models/Branch');
|
||||
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
||||
|
||||
return {
|
||||
paymentAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'expenses_transactions.paymentAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
categories: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ExpenseCategory.default,
|
||||
join: {
|
||||
from: 'expenses_transactions.id',
|
||||
to: 'expense_transaction_categories.expenseId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Expense transction may belongs to a branch.
|
||||
*/
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch.default,
|
||||
join: {
|
||||
from: 'expenses_transactions.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Expense transaction may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'expenses_transactions.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'Expense');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Expense may belongs to matched bank transaction.
|
||||
*/
|
||||
matchedBankTransaction: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: MatchedBankTransaction,
|
||||
join: {
|
||||
from: 'expenses_transactions.id',
|
||||
to: 'matched_bank_transactions.referenceId',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('reference_type', 'Expense');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get meta() {
|
||||
return ExpenseSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'reference_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class ExpenseCategory extends TenantModel {
|
||||
amount: number;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'expense_transaction_categories';
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['unallocatedCostAmount'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remain unallocated landed cost.
|
||||
* @return {number}
|
||||
*/
|
||||
get unallocatedCostAmount() {
|
||||
return Math.max(this.amount - this.allocatedCostAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Account = require('models/Account');
|
||||
|
||||
return {
|
||||
expenseAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'expense_transaction_categories.expenseAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
export default {
|
||||
defaultFilterField: 'date',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'date',
|
||||
},
|
||||
columns: {
|
||||
date: {
|
||||
name: 'inventory_adjustment.field.date',
|
||||
column: 'date',
|
||||
fieldType: 'date',
|
||||
exportable: true,
|
||||
},
|
||||
type: {
|
||||
name: 'inventory_adjustment.field.type',
|
||||
column: 'type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'increment', name: 'inventory_adjustment.field.type.increment' },
|
||||
{ key: 'decrement', name: 'inventory_adjustment.field.type.decrement' },
|
||||
],
|
||||
exportable: true,
|
||||
},
|
||||
adjustmentAccount: {
|
||||
name: 'inventory_adjustment.field.adjustment_account',
|
||||
type: 'adjustment_account_id',
|
||||
exportable: true,
|
||||
},
|
||||
reason: {
|
||||
name: 'inventory_adjustment.field.reason',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'inventory_adjustment.field.reference_no',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
description: {
|
||||
name: 'inventory_adjustment.field.description',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
publishedAt: {
|
||||
name: 'inventory_adjustment.field.published_at',
|
||||
type: 'date',
|
||||
exportable: true,
|
||||
},
|
||||
createdAt: {
|
||||
name: 'inventory_adjustment.field.created_at',
|
||||
type: 'date',
|
||||
exportable: true,
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
date: {
|
||||
name: 'inventory_adjustment.field.date',
|
||||
column: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
type: {
|
||||
name: 'inventory_adjustment.field.type',
|
||||
column: 'type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'increment', name: 'inventory_adjustment.field.type.increment' },
|
||||
{ key: 'decrement', name: 'inventory_adjustment.field.type.decrement' },
|
||||
],
|
||||
},
|
||||
adjustment_account: {
|
||||
name: 'inventory_adjustment.field.adjustment_account',
|
||||
column: 'adjustment_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'adjustmentAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
reason: {
|
||||
name: 'inventory_adjustment.field.reason',
|
||||
column: 'reason',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'inventory_adjustment.field.reference_no',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'inventory_adjustment.field.description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
published_at: {
|
||||
name: 'inventory_adjustment.field.published_at',
|
||||
column: 'published_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
created_at: {
|
||||
name: 'inventory_adjustment.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,113 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import InventoryAdjustmentSettings from './InventoryAdjustment.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
|
||||
export default class InventoryAdjustment extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'inventory_adjustments';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['formattedType', 'inventoryDirection', 'isPublished'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve formatted adjustment type.
|
||||
*/
|
||||
get formattedType() {
|
||||
return InventoryAdjustment.getFormattedType(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve formatted reference type.
|
||||
*/
|
||||
get inventoryDirection() {
|
||||
return InventoryAdjustment.getInventoryDirection(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the adjustment is published.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPublished() {
|
||||
return !!this.publishedAt;
|
||||
}
|
||||
|
||||
static getInventoryDirection(type) {
|
||||
const directions = {
|
||||
increment: 'IN',
|
||||
decrement: 'OUT',
|
||||
};
|
||||
return directions[type] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the formatted adjustment type of the given type.
|
||||
* @param {string} type
|
||||
* @returns {string}
|
||||
*/
|
||||
static getFormattedType(type) {
|
||||
const types = {
|
||||
increment: 'inventory_adjustment.type.increment',
|
||||
decrement: 'inventory_adjustment.type.decrement',
|
||||
};
|
||||
return types[type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const InventoryAdjustmentEntry = require('models/InventoryAdjustmentEntry');
|
||||
const Account = require('models/Account');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Adjustment entries.
|
||||
*/
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: InventoryAdjustmentEntry.default,
|
||||
join: {
|
||||
from: 'inventory_adjustments.id',
|
||||
to: 'inventory_adjustments_entries.adjustmentId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Inventory adjustment account.
|
||||
*/
|
||||
adjustmentAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'inventory_adjustments.adjustmentAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return InventoryAdjustmentSettings;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class InventoryAdjustmentEntry extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'inventory_adjustments_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const InventoryAdjustment = require('models/InventoryAdjustment');
|
||||
const Item = require('models/Item');
|
||||
|
||||
return {
|
||||
inventoryAdjustment: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: InventoryAdjustment.default,
|
||||
join: {
|
||||
from: 'inventory_adjustments_entries.adjustmentId',
|
||||
to: 'inventory_adjustments.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Entry item.
|
||||
*/
|
||||
item: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Item.default,
|
||||
join: {
|
||||
from: 'inventory_adjustments_entries.itemId',
|
||||
to: 'items.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import { castArray, isEmpty } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class InventoryCostLotTracker extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'inventory_cost_lot_tracker';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
static get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
groupedEntriesCost(query) {
|
||||
query.select(['date', 'item_id', 'transaction_id', 'transaction_type']);
|
||||
query.sum('cost as cost');
|
||||
|
||||
query.groupBy('transaction_id');
|
||||
query.groupBy('transaction_type');
|
||||
query.groupBy('date');
|
||||
query.groupBy('item_id');
|
||||
},
|
||||
filterDateRange(query, startDate, endDate, type = 'day') {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const fromDate = moment(startDate).startOf(type).format(dateFormat);
|
||||
const toDate = moment(endDate).endOf(type).format(dateFormat);
|
||||
|
||||
if (startDate) {
|
||||
query.where('date', '>=', fromDate);
|
||||
}
|
||||
if (endDate) {
|
||||
query.where('date', '<=', toDate);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters transactions by the given branches.
|
||||
*/
|
||||
filterByBranches(query, branchesIds) {
|
||||
const formattedBranchesIds = castArray(branchesIds);
|
||||
|
||||
query.whereIn('branchId', formattedBranchesIds);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters transactions by the given warehosues.
|
||||
*/
|
||||
filterByWarehouses(query, branchesIds) {
|
||||
const formattedWarehousesIds = castArray(branchesIds);
|
||||
|
||||
query.whereIn('warehouseId', formattedWarehousesIds);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Item = require('models/Item');
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const SaleReceipt = require('models/SaleReceipt');
|
||||
|
||||
return {
|
||||
item: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Item.default,
|
||||
join: {
|
||||
from: 'inventory_cost_lot_tracker.itemId',
|
||||
to: 'items.id',
|
||||
},
|
||||
},
|
||||
invoice: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'inventory_cost_lot_tracker.transactionId',
|
||||
to: 'sales_invoices.id',
|
||||
},
|
||||
},
|
||||
itemEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'inventory_cost_lot_tracker.entryId',
|
||||
to: 'items_entries.id',
|
||||
},
|
||||
},
|
||||
receipt: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleReceipt.default,
|
||||
join: {
|
||||
from: 'inventory_cost_lot_tracker.transactionId',
|
||||
to: 'sales_receipts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { getTransactionTypeLabel } from '@/utils/transactions-types';
|
||||
|
||||
export default class InventoryTransaction extends TenantModel {
|
||||
transactionId: number;
|
||||
transactionType: string;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'inventory_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve formatted reference type.
|
||||
* @return {string}
|
||||
*/
|
||||
get transcationTypeFormatted() {
|
||||
return getTransactionTypeLabel(this.transactionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
filterDateRange(query, startDate, endDate, type = 'day') {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const fromDate = moment(startDate).startOf(type).format(dateFormat);
|
||||
const toDate = moment(endDate).endOf(type).format(dateFormat);
|
||||
|
||||
if (startDate) {
|
||||
query.where('date', '>=', fromDate);
|
||||
}
|
||||
if (endDate) {
|
||||
query.where('date', '<=', toDate);
|
||||
}
|
||||
},
|
||||
|
||||
itemsTotals(builder) {
|
||||
builder.select('itemId');
|
||||
builder.sum('rate as rate');
|
||||
builder.sum('quantity as quantity');
|
||||
builder.select(raw('SUM(`QUANTITY` * `RATE`) as COST'));
|
||||
builder.groupBy('itemId');
|
||||
},
|
||||
|
||||
INDirection(builder) {
|
||||
builder.where('direction', 'IN');
|
||||
},
|
||||
|
||||
OUTDirection(builder) {
|
||||
builder.where('direction', 'OUT');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters transactions by the given branches.
|
||||
*/
|
||||
filterByBranches(query, branchesIds) {
|
||||
const formattedBranchesIds = castArray(branchesIds);
|
||||
|
||||
query.whereIn('branch_id', formattedBranchesIds);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters transactions by the given warehosues.
|
||||
*/
|
||||
filterByWarehouses(query, warehousesIds) {
|
||||
const formattedWarehousesIds = castArray(warehousesIds);
|
||||
|
||||
query.whereIn('warehouse_id', formattedWarehousesIds);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Item = require('models/Item');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const InventoryTransactionMeta = require('models/InventoryTransactionMeta');
|
||||
const InventoryCostLots = require('models/InventoryCostLotTracker');
|
||||
|
||||
return {
|
||||
// Transaction meta.
|
||||
meta: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: InventoryTransactionMeta.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.id',
|
||||
to: 'inventory_transaction_meta.inventoryTransactionId',
|
||||
},
|
||||
},
|
||||
// Item cost aggregated.
|
||||
itemCostAggregated: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: InventoryCostLots.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.itemId',
|
||||
to: 'inventory_cost_lot_tracker.itemId',
|
||||
},
|
||||
filter(query) {
|
||||
query.select('itemId');
|
||||
query.sum('cost as cost');
|
||||
query.sum('quantity as quantity');
|
||||
query.groupBy('itemId');
|
||||
},
|
||||
},
|
||||
costLotAggregated: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: InventoryCostLots.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.id',
|
||||
to: 'inventory_cost_lot_tracker.inventoryTransactionId',
|
||||
},
|
||||
filter(query) {
|
||||
query.sum('cost as cost');
|
||||
query.sum('quantity as quantity');
|
||||
query.groupBy('inventoryTransactionId');
|
||||
},
|
||||
},
|
||||
item: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Item.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.itemId',
|
||||
to: 'items.id',
|
||||
},
|
||||
},
|
||||
itemEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.entryId',
|
||||
to: 'items_entries.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class InventoryTransactionMeta extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'inventory_transaction_meta';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const InventoryTransactions = require('models/InventoryTransaction');
|
||||
|
||||
return {
|
||||
inventoryTransaction: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: InventoryTransactions.default,
|
||||
join: {
|
||||
from: 'inventory_transaction_meta.inventoryTransactionId',
|
||||
to: 'inventory_transactions.inventoryTransactionId'
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
export default {
|
||||
importable: true,
|
||||
exportable: true,
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortField: 'name',
|
||||
sortOrder: 'DESC',
|
||||
},
|
||||
print: {
|
||||
pageTitle: 'Items',
|
||||
},
|
||||
fields: {
|
||||
type: {
|
||||
name: 'item.field.type',
|
||||
column: 'type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'inventory', label: 'item.field.type.inventory' },
|
||||
{ key: 'service', label: 'item.field.type.service' },
|
||||
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
|
||||
],
|
||||
},
|
||||
name: {
|
||||
name: 'item.field.name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
code: {
|
||||
name: 'item.field.code',
|
||||
column: 'code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
sellable: {
|
||||
name: 'item.field.sellable',
|
||||
column: 'sellable',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
purchasable: {
|
||||
name: 'item.field.purchasable',
|
||||
column: 'purchasable',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
sell_price: {
|
||||
name: 'item.field.sell_price',
|
||||
column: 'sell_price',
|
||||
fieldType: 'number',
|
||||
},
|
||||
cost_price: {
|
||||
name: 'item.field.cost_price',
|
||||
column: 'cost_price',
|
||||
fieldType: 'number',
|
||||
},
|
||||
cost_account: {
|
||||
name: 'item.field.cost_account',
|
||||
column: 'cost_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'costAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
sell_account: {
|
||||
name: 'item.field.sell_account',
|
||||
column: 'sell_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'sellAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
inventory_account: {
|
||||
name: 'item.field.inventory_account',
|
||||
column: 'inventory_account_id',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'inventoryAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
sell_description: {
|
||||
name: 'Sell description',
|
||||
column: 'sell_description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
purchase_description: {
|
||||
name: 'Purchase description',
|
||||
column: 'purchase_description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
quantity_on_hand: {
|
||||
name: 'item.field.quantity_on_hand',
|
||||
column: 'quantity_on_hand',
|
||||
fieldType: 'number',
|
||||
},
|
||||
note: {
|
||||
name: 'item.field.note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
category: {
|
||||
name: 'item.field.category',
|
||||
column: 'category_id',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'category',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
active: {
|
||||
name: 'item.field.active',
|
||||
column: 'active',
|
||||
fieldType: 'boolean',
|
||||
filterable: false,
|
||||
},
|
||||
created_at: {
|
||||
name: 'item.field.created_at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
type: {
|
||||
name: 'item.field.type',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
accessor: 'typeFormatted',
|
||||
},
|
||||
name: {
|
||||
name: 'item.field.name',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
code: {
|
||||
name: 'item.field.code',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
sellable: {
|
||||
name: 'item.field.sellable',
|
||||
type: 'boolean',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
purchasable: {
|
||||
name: 'item.field.purchasable',
|
||||
type: 'boolean',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
sellPrice: {
|
||||
name: 'item.field.sell_price',
|
||||
type: 'number',
|
||||
exportable: true,
|
||||
},
|
||||
costPrice: {
|
||||
name: 'item.field.cost_price',
|
||||
type: 'number',
|
||||
exportable: true,
|
||||
},
|
||||
costAccount: {
|
||||
name: 'item.field.cost_account',
|
||||
type: 'text',
|
||||
accessor: 'costAccount.name',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
sellAccount: {
|
||||
name: 'item.field.sell_account',
|
||||
type: 'text',
|
||||
accessor: 'sellAccount.name',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
inventoryAccount: {
|
||||
name: 'item.field.inventory_account',
|
||||
type: 'text',
|
||||
accessor: 'inventoryAccount.name',
|
||||
exportable: true,
|
||||
},
|
||||
sellDescription: {
|
||||
name: 'Sell description',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
purchaseDescription: {
|
||||
name: 'Purchase description',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
quantityOnHand: {
|
||||
name: 'item.field.quantity_on_hand',
|
||||
type: 'number',
|
||||
exportable: true,
|
||||
},
|
||||
note: {
|
||||
name: 'item.field.note',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
category: {
|
||||
name: 'item.field.category',
|
||||
type: 'text',
|
||||
accessor: 'category.name',
|
||||
exportable: true,
|
||||
},
|
||||
active: {
|
||||
name: 'item.field.active',
|
||||
fieldType: 'boolean',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
createdAt: {
|
||||
name: 'item.field.created_at',
|
||||
type: 'date',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
type: {
|
||||
name: 'item.field.type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'inventory', label: 'item.field.type.inventory' },
|
||||
{ key: 'service', label: 'item.field.type.service' },
|
||||
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
name: 'item.field.name',
|
||||
fieldType: 'text',
|
||||
required: true,
|
||||
},
|
||||
code: {
|
||||
name: 'item.field.code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
sellable: {
|
||||
name: 'item.field.sellable',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
purchasable: {
|
||||
name: 'item.field.purchasable',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
sellPrice: {
|
||||
name: 'item.field.sell_price',
|
||||
fieldType: 'number',
|
||||
},
|
||||
costPrice: {
|
||||
name: 'item.field.cost_price',
|
||||
fieldType: 'number',
|
||||
},
|
||||
costAccountId: {
|
||||
name: 'item.field.cost_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
sellAccountId: {
|
||||
name: 'item.field.sell_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
inventoryAccountId: {
|
||||
name: 'item.field.inventory_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
sellDescription: {
|
||||
name: 'Sell Description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
purchaseDescription: {
|
||||
name: 'Purchase Description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
note: {
|
||||
name: 'item.field.note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
categoryId: {
|
||||
name: 'item.field.category',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'ItemCategory',
|
||||
relationImportMatch: ['name'],
|
||||
importHint: 'Matches the category name.',
|
||||
},
|
||||
active: {
|
||||
name: 'item.field.active',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,243 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { buildFilterQuery } from '@/lib/ViewRolesBuilder';
|
||||
import ItemSettings from './Item.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Items/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class Item extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'items';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to mark model as resourceable to viewable and filterable.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
sortBy(query, columnSort, sortDirection) {
|
||||
query.orderBy(columnSort, sortDirection);
|
||||
},
|
||||
viewRolesBuilder(query, conditions, logicExpression) {
|
||||
buildFilterQuery(Item.tableName, conditions, logicExpression)(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('items.active', !active);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Media = require('models/Media');
|
||||
const Account = require('models/Account');
|
||||
const ItemCategory = require('models/ItemCategory');
|
||||
const ItemWarehouseQuantity = require('models/ItemWarehouseQuantity');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const WarehouseTransferEntry = require('models/WarehouseTransferEntry');
|
||||
const InventoryAdjustmentEntry = require('models/InventoryAdjustmentEntry');
|
||||
const TaxRate = require('models/TaxRate');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Item may belongs to cateogory model.
|
||||
*/
|
||||
category: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemCategory.default,
|
||||
join: {
|
||||
from: 'items.categoryId',
|
||||
to: 'items_categories.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item may belongs to cost account.
|
||||
*/
|
||||
costAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'items.costAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item may belongs to sell account.
|
||||
*/
|
||||
sellAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'items.sellAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item may belongs to inventory account.
|
||||
*/
|
||||
inventoryAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'items.inventoryAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item has many warehouses quantities.
|
||||
*/
|
||||
itemWarehouses: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemWarehouseQuantity.default,
|
||||
join: {
|
||||
from: 'items.id',
|
||||
to: 'items_warehouses_quantity.itemId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item may has many item entries.
|
||||
*/
|
||||
itemEntries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'items.id',
|
||||
to: 'items_entries.itemId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item may has many warehouses transfers entries.
|
||||
*/
|
||||
warehousesTransfersEntries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: WarehouseTransferEntry.default,
|
||||
join: {
|
||||
from: 'items.id',
|
||||
to: 'warehouses_transfers_entries.itemId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item has many inventory adjustment entries.
|
||||
*/
|
||||
inventoryAdjustmentsEntries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: InventoryAdjustmentEntry.default,
|
||||
join: {
|
||||
from: 'items.id',
|
||||
to: 'inventory_adjustments_entries.itemId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
media: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Media.default,
|
||||
join: {
|
||||
from: 'items.id',
|
||||
through: {
|
||||
from: 'media_links.model_id',
|
||||
to: 'media_links.media_id',
|
||||
},
|
||||
to: 'media.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item may has sell tax rate.
|
||||
*/
|
||||
sellTaxRate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TaxRate.default,
|
||||
join: {
|
||||
from: 'items.sellTaxRateId',
|
||||
to: 'tax_rates.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Item may has purchase tax rate.
|
||||
*/
|
||||
purchaseTaxRate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TaxRate.default,
|
||||
join: {
|
||||
from: 'items.purchaseTaxRateId',
|
||||
to: 'tax_rates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static get secureDeleteRelations() {
|
||||
return [
|
||||
'itemEntries',
|
||||
'inventoryAdjustmentsEntries',
|
||||
'warehousesTransfersEntries',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return ItemSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search roles.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortField: 'name',
|
||||
sortOrder: 'DESC',
|
||||
},
|
||||
importable: true,
|
||||
exportable: true,
|
||||
fields: {
|
||||
name: {
|
||||
name: 'item_category.field.name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'item_category.field.description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
count: {
|
||||
name: 'item_category.field.count',
|
||||
column: 'count',
|
||||
fieldType: 'number',
|
||||
virtualColumn: true,
|
||||
},
|
||||
created_at: {
|
||||
name: 'item_category.field.created_at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
name: {
|
||||
name: 'item_category.field.name',
|
||||
type: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'item_category.field.description',
|
||||
type: 'text',
|
||||
},
|
||||
count: {
|
||||
name: 'item_category.field.count',
|
||||
type: 'text',
|
||||
},
|
||||
createdAt: {
|
||||
name: 'item_category.field.created_at',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
name: {
|
||||
name: 'item_category.field.name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'item_category.field.description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ItemCategorySettings from './ItemCategory.Settings';
|
||||
|
||||
export default class ItemCategory extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'items_categories';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Item = require('models/Item');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Item category may has many items.
|
||||
*/
|
||||
items: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Item.default,
|
||||
join: {
|
||||
from: 'items_categories.id',
|
||||
to: 'items.categoryId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
sortByCount(query, order = 'asc') {
|
||||
query.orderBy('count', order);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model meta.
|
||||
*/
|
||||
static get meta() {
|
||||
return ItemCategorySettings;
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { getExlusiveTaxAmount, getInclusiveTaxAmount } from '@/utils/taxRate';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
|
||||
// Subtotal (qty * rate) (tax inclusive)
|
||||
// Subtotal Tax Exclusive (Subtotal - Tax Amount)
|
||||
// Discount (Is percentage ? amount * discount : discount)
|
||||
// Total (Subtotal - Discount)
|
||||
|
||||
export default class ItemEntry extends TenantModel {
|
||||
public taxRate: number;
|
||||
public discount: number;
|
||||
public quantity: number;
|
||||
public rate: number;
|
||||
public isInclusiveTax: number;
|
||||
public discountType: DiscountType;
|
||||
/**
|
||||
* Table name.
|
||||
* @returns {string}
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'items_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
// Amount (qty * rate)
|
||||
'amount',
|
||||
|
||||
'taxAmount',
|
||||
|
||||
// Subtotal (qty * rate) + (tax inclusive)
|
||||
'subtotalInclusingTax',
|
||||
|
||||
// Subtotal Tax Exclusive (Subtotal - Tax Amount)
|
||||
'subtotalExcludingTax',
|
||||
|
||||
// Subtotal (qty * rate) + (tax inclusive)
|
||||
'subtotal',
|
||||
|
||||
// Discount (Is percentage ? amount * discount : discount)
|
||||
'discountAmount',
|
||||
|
||||
// Total (Subtotal - Discount)
|
||||
'total',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Item entry total.
|
||||
* Amount of item entry includes tax and subtracted discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.subtotal - this.discountAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Total (excluding tax).
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalExcludingTax() {
|
||||
return this.subtotalExcludingTax - this.discountAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Item entry amount.
|
||||
* Amount of item entry that may include or exclude tax.
|
||||
* @returns {number}
|
||||
*/
|
||||
get amount() {
|
||||
return this.quantity * this.rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal amount (tax inclusive).
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.subtotalInclusingTax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Item entry amount including tax.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalInclusingTax() {
|
||||
return this.isInclusiveTax ? this.amount : this.amount + this.taxAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal amount (tax exclusive).
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalExcludingTax() {
|
||||
return this.isInclusiveTax ? this.amount - this.taxAmount : this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Percentage
|
||||
? this.amount * (this.discount / 100)
|
||||
: this.discount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag rate fraction.
|
||||
* @returns {number}
|
||||
*/
|
||||
get tagRateFraction() {
|
||||
return this.taxRate / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tax amount withheld.
|
||||
* @returns {number}
|
||||
*/
|
||||
get taxAmount() {
|
||||
return this.isInclusiveTax
|
||||
? getInclusiveTaxAmount(this.amount, this.taxRate)
|
||||
: getExlusiveTaxAmount(this.amount, this.taxRate);
|
||||
}
|
||||
|
||||
static calcAmount(itemEntry) {
|
||||
const { discount, quantity, rate } = itemEntry;
|
||||
const total = quantity * rate;
|
||||
|
||||
return discount ? total - total * discount * 0.01 : total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Item entry relations.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Item = require('models/Item');
|
||||
const BillLandedCostEntry = require('models/BillLandedCostEntry');
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
const Bill = require('models/Bill');
|
||||
const SaleReceipt = require('models/SaleReceipt');
|
||||
const SaleEstimate = require('models/SaleEstimate');
|
||||
const ProjectTask = require('models/Task');
|
||||
const Expense = require('models/Expense');
|
||||
const TaxRate = require('models/TaxRate');
|
||||
|
||||
return {
|
||||
item: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Item.default,
|
||||
join: {
|
||||
from: 'items_entries.itemId',
|
||||
to: 'items.id',
|
||||
},
|
||||
},
|
||||
allocatedCostEntries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: BillLandedCostEntry.default,
|
||||
join: {
|
||||
from: 'items_entries.referenceId',
|
||||
to: 'bill_located_cost_entries.entryId',
|
||||
},
|
||||
},
|
||||
|
||||
invoice: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'items_entries.referenceId',
|
||||
to: 'sales_invoices.id',
|
||||
},
|
||||
},
|
||||
|
||||
bill: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Bill.default,
|
||||
join: {
|
||||
from: 'items_entries.referenceId',
|
||||
to: 'bills.id',
|
||||
},
|
||||
},
|
||||
|
||||
estimate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleEstimate.default,
|
||||
join: {
|
||||
from: 'items_entries.referenceId',
|
||||
to: 'sales_estimates.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale receipt reference.
|
||||
*/
|
||||
receipt: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleReceipt.default,
|
||||
join: {
|
||||
from: 'items_entries.referenceId',
|
||||
to: 'sales_receipts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Project task reference.
|
||||
*/
|
||||
projectTaskRef: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ProjectTask.default,
|
||||
join: {
|
||||
from: 'items_entries.projectRefId',
|
||||
to: 'tasks.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Project expense reference.
|
||||
*/
|
||||
projectExpenseRef: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Expense.default,
|
||||
join: {
|
||||
from: 'items_entries.projectRefId',
|
||||
to: 'expenses_transactions.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Project bill reference.
|
||||
*/
|
||||
projectBillRef: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Bill.default,
|
||||
join: {
|
||||
from: 'items_entries.projectRefId',
|
||||
to: 'bills.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Tax rate reference.
|
||||
*/
|
||||
tax: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: TaxRate.default,
|
||||
join: {
|
||||
from: 'items_entries.taxRateId',
|
||||
to: 'tax_rates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class ItemWarehouseQuantity extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'items_warehouses_quantity';
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
const Item = require('models/Item');
|
||||
const Warehouse = require('models/Warehouse');
|
||||
|
||||
return {
|
||||
item: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Item.default,
|
||||
join: {
|
||||
from: 'items_warehouses_quantity.itemId',
|
||||
to: 'items.id',
|
||||
},
|
||||
},
|
||||
warehouse: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Warehouse.default,
|
||||
join: {
|
||||
from: 'items_warehouses_quantity.warehouseId',
|
||||
to: 'warehouses.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
export default {
|
||||
defaultFilterField: 'date',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
importable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
exportable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'journalNumber',
|
||||
|
||||
print: {
|
||||
pageTitle: 'Manual Journals',
|
||||
},
|
||||
|
||||
fields: {
|
||||
date: {
|
||||
name: 'manual_journal.field.date',
|
||||
column: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
journal_number: {
|
||||
name: 'manual_journal.field.journal_number',
|
||||
column: 'journal_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference: {
|
||||
name: 'manual_journal.field.reference',
|
||||
column: 'reference',
|
||||
fieldType: 'text',
|
||||
},
|
||||
journal_type: {
|
||||
name: 'manual_journal.field.journal_type',
|
||||
column: 'journal_type',
|
||||
fieldType: 'text',
|
||||
},
|
||||
amount: {
|
||||
name: 'manual_journal.field.amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
description: {
|
||||
name: 'manual_journal.field.description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
name: 'manual_journal.field.status',
|
||||
column: 'status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', label: 'Draft' },
|
||||
{ key: 'published', label: 'published' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
created_at: {
|
||||
name: 'manual_journal.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
date: {
|
||||
name: 'manual_journal.field.date',
|
||||
type: 'date',
|
||||
accessor: 'formattedDate',
|
||||
},
|
||||
journalNumber: {
|
||||
name: 'manual_journal.field.journal_number',
|
||||
type: 'text',
|
||||
},
|
||||
reference: {
|
||||
name: 'manual_journal.field.reference',
|
||||
type: 'text',
|
||||
},
|
||||
journalType: {
|
||||
name: 'manual_journal.field.journal_type',
|
||||
type: 'text',
|
||||
},
|
||||
amount: {
|
||||
name: 'Amount',
|
||||
accessor: 'formattedAmount',
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'manual_journal.field.currency',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'manual_journal.field.exchange_rate',
|
||||
type: 'number',
|
||||
printable: false,
|
||||
},
|
||||
description: {
|
||||
name: 'manual_journal.field.description',
|
||||
type: 'text',
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
type: 'collection',
|
||||
collectionOf: 'object',
|
||||
columns: {
|
||||
credit: {
|
||||
name: 'Credit',
|
||||
type: 'text',
|
||||
},
|
||||
debit: {
|
||||
name: 'Debit',
|
||||
type: 'text',
|
||||
},
|
||||
account: {
|
||||
name: 'Account',
|
||||
accessor: 'account.name',
|
||||
},
|
||||
contact: {
|
||||
name: 'Contact',
|
||||
accessor: 'contact.displayName',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
},
|
||||
},
|
||||
publish: {
|
||||
name: 'Publish',
|
||||
type: 'boolean',
|
||||
printable: false,
|
||||
},
|
||||
publishedAt: {
|
||||
name: 'Published At',
|
||||
printable: false,
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
name: 'Created At',
|
||||
accessor: 'formattedCreatedAt',
|
||||
printable: false,
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
date: {
|
||||
name: 'manual_journal.field.date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
journalNumber: {
|
||||
name: 'manual_journal.field.journal_number',
|
||||
fieldType: 'text',
|
||||
required: true,
|
||||
},
|
||||
reference: {
|
||||
name: 'manual_journal.field.reference',
|
||||
fieldType: 'text',
|
||||
},
|
||||
journalType: {
|
||||
name: 'manual_journal.field.journal_type',
|
||||
fieldType: 'text',
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'manual_journal.field.currency',
|
||||
fieldType: 'text',
|
||||
},
|
||||
exchange_rate: {
|
||||
name: 'manual_journal.field.exchange_rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
description: {
|
||||
name: 'manual_journal.field.description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
fieldType: 'collection',
|
||||
collectionOf: 'object',
|
||||
collectionMinLength: 2,
|
||||
required: true,
|
||||
fields: {
|
||||
credit: {
|
||||
name: 'Credit',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
debit: {
|
||||
name: 'Debit',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
accountId: {
|
||||
name: 'Account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
},
|
||||
contact: {
|
||||
name: 'Contact',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Contact',
|
||||
relationImportMatch: 'displayName',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
publish: {
|
||||
name: 'Publish',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Status field sorting custom query.
|
||||
*/
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
return query.modify('sortByStatus', role.order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status field filter custom query.
|
||||
*/
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { formatNumber } from 'utils';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ManualJournalSettings from './ManualJournal.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/ManualJournals/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
export default class ManualJournal extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'manual_journals';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['isPublished', 'amountFormatted'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the amount formatted value.
|
||||
*/
|
||||
get amountFormatted() {
|
||||
return formatNumber(this.amount, { currencyCode: this.currencyCode });
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the invoice is published.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPublished() {
|
||||
return !!this.publishedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Sort by status query.
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(`PUBLISHED_AT IS NULL ${order}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by draft status.
|
||||
*/
|
||||
filterByDraft(query) {
|
||||
query.whereNull('publishedAt');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by published status.
|
||||
*/
|
||||
filterByPublished(query) {
|
||||
query.whereNotNull('publishedAt');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by the given status.
|
||||
*/
|
||||
filterByStatus(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('filterByDraft');
|
||||
break;
|
||||
case 'published':
|
||||
default:
|
||||
query.modify('filterByPublished');
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const ManualJournalEntry = require('models/ManualJournalEntry');
|
||||
const Document = require('models/Document');
|
||||
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
||||
|
||||
return {
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ManualJournalEntry.default,
|
||||
join: {
|
||||
from: 'manual_journals.id',
|
||||
to: 'manual_journals_entries.manualJournalId',
|
||||
},
|
||||
filter(query) {
|
||||
query.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'manual_journals.id',
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.where('referenceType', 'Journal');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Manual journal may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'manual_journals.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'ManualJournal');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Manual journal may belongs to matched bank transaction.
|
||||
*/
|
||||
matchedBankTransaction: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: MatchedBankTransaction,
|
||||
join: {
|
||||
from: 'manual_journals.id',
|
||||
to: 'matched_bank_transactions.referenceId',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('reference_type', 'ManualJournal');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get meta() {
|
||||
return ManualJournalSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'journal_number', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class ManualJournalEntry extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'manual_journals_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Account = require('models/Account');
|
||||
const Contact = require('models/Contact');
|
||||
const Branch = require('models/Branch');
|
||||
|
||||
return {
|
||||
account: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'manual_journals_entries.accountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
contact: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact.default,
|
||||
join: {
|
||||
from: 'manual_journals_entries.contactId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
},
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch.default,
|
||||
join: {
|
||||
from: 'manual_journals_entries.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { Model } from 'objection';
|
||||
|
||||
export class MatchedBankTransaction extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'matched_bank_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class Media extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'media';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const MediaLink = require('models/MediaLink');
|
||||
|
||||
return {
|
||||
links: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: MediaLink.default,
|
||||
join: {
|
||||
from: 'media.id',
|
||||
to: 'media_links.media_id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class MediaLink extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'media_links';
|
||||
}
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
import knex from '@/database/knex';
|
||||
// import cache from 'memory-cache';
|
||||
|
||||
// Metadata
|
||||
export default {
|
||||
METADATA_GROUP: 'default',
|
||||
KEY_COLUMN: 'key',
|
||||
VALUE_COLUMN: 'value',
|
||||
TYPE_COLUMN: 'type',
|
||||
|
||||
extraColumns: [],
|
||||
metadata: [],
|
||||
shouldReload: true,
|
||||
extraMetadataQuery: () => {},
|
||||
|
||||
/**
|
||||
* Set the value column key to query from.
|
||||
* @param {String} name -
|
||||
*/
|
||||
setKeyColumnName(name) {
|
||||
this.KEY_COLUMN = name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the key column name to query from.
|
||||
* @param {String} name -
|
||||
*/
|
||||
setValueColumnName(name) {
|
||||
this.VALUE_COLUMN = name;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set extra columns to be added to the rows.
|
||||
* @param {Array} columns -
|
||||
*/
|
||||
setExtraColumns(columns) {
|
||||
this.extraColumns = columns;
|
||||
},
|
||||
|
||||
/**
|
||||
* Metadata database query.
|
||||
* @param {Object} query -
|
||||
* @param {String} groupName -
|
||||
*/
|
||||
whereQuery(query, key) {
|
||||
const groupName = this.METADATA_GROUP;
|
||||
|
||||
if (groupName) {
|
||||
query.where('group', groupName);
|
||||
}
|
||||
if (key) {
|
||||
if (Array.isArray(key)) {
|
||||
query.whereIn('key', key);
|
||||
} else {
|
||||
query.where('key', key);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the metadata from the storage.
|
||||
* @param {String|Array} key -
|
||||
* @param {Boolean} force -
|
||||
*/
|
||||
async load(force = false) {
|
||||
if (this.shouldReload || force) {
|
||||
const metadataCollection = await this.query((query) => {
|
||||
this.whereQuery(query);
|
||||
this.extraMetadataQuery(query);
|
||||
}).fetchAll();
|
||||
|
||||
this.shouldReload = false;
|
||||
this.metadata = [];
|
||||
|
||||
const metadataArray = this.mapMetadataCollection(metadataCollection);
|
||||
metadataArray.forEach((metadata) => { this.metadata.push(metadata); });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches all the metadata that associate with the current group.
|
||||
*/
|
||||
async allMeta(force = false) {
|
||||
await this.load(force);
|
||||
return this.metadata;
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the given metadata key.
|
||||
* @param {String} key -
|
||||
* @return {object} - Metadata object.
|
||||
*/
|
||||
findMeta(key) {
|
||||
return this.metadata.find((meta) => meta.key === key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the metadata of the current group.
|
||||
* @param {*} key -
|
||||
*/
|
||||
async getMeta(key, defaultValue, force = false) {
|
||||
await this.load(force);
|
||||
|
||||
const metadata = this.findMeta(key);
|
||||
return metadata ? metadata.value : defaultValue || false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Markes the metadata to should be deleted.
|
||||
* @param {String} key -
|
||||
*/
|
||||
async removeMeta(key) {
|
||||
await this.load();
|
||||
const metadata = this.findMeta(key);
|
||||
|
||||
if (metadata) {
|
||||
metadata.markAsDeleted = true;
|
||||
}
|
||||
this.shouldReload = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all meta data of the given group.
|
||||
* @param {*} group
|
||||
*/
|
||||
removeAllMeta(group = 'default') {
|
||||
this.metdata.map((meta) => ({
|
||||
...(meta.group !== group) ? { markAsDeleted: true } : {},
|
||||
...meta,
|
||||
}));
|
||||
this.shouldReload = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the meta data to the stack.
|
||||
* @param {String} key -
|
||||
* @param {String} value -
|
||||
*/
|
||||
async setMeta(key, value, payload) {
|
||||
if (Array.isArray(key)) {
|
||||
const metadata = key;
|
||||
metadata.forEach((meta) => {
|
||||
this.setMeta(meta.key, meta.value);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.load();
|
||||
const metadata = this.findMeta(key);
|
||||
|
||||
if (metadata) {
|
||||
metadata.value = value;
|
||||
metadata.markAsUpdated = true;
|
||||
} else {
|
||||
this.metadata.push({
|
||||
value, key, ...payload, markAsInserted: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Saved the modified metadata.
|
||||
*/
|
||||
async saveMeta() {
|
||||
const inserted = this.metadata.filter((m) => (m.markAsInserted === true));
|
||||
const updated = this.metadata.filter((m) => (m.markAsUpdated === true));
|
||||
const deleted = this.metadata.filter((m) => (m.markAsDeleted === true));
|
||||
|
||||
const metadataDeletedKeys = deleted.map((m) => m.key);
|
||||
const metadataInserted = inserted.map((m) => this.mapMetadata(m, 'format'));
|
||||
const metadataUpdated = updated.map((m) => this.mapMetadata(m, 'format'));
|
||||
|
||||
const batchUpdate = (collection) => knex.transaction((trx) => {
|
||||
const queries = collection.map((tuple) => {
|
||||
const query = knex(this.tableName);
|
||||
this.whereQuery(query, tuple.key);
|
||||
this.extraMetadataQuery(query);
|
||||
return query.update(tuple).transacting(trx);
|
||||
});
|
||||
return Promise.all(queries).then(trx.commit).catch(trx.rollback);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
knex.insert(metadataInserted).into(this.tableName),
|
||||
batchUpdate(metadataUpdated),
|
||||
metadataDeletedKeys.length > 0
|
||||
? this.query('whereIn', this.KEY_COLUMN, metadataDeletedKeys).destroy({
|
||||
require: true,
|
||||
}) : null,
|
||||
]);
|
||||
this.shouldReload = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Purge all the cached metadata in the memory.
|
||||
*/
|
||||
purgeMetadata() {
|
||||
this.metadata = [];
|
||||
this.shouldReload = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses the metadata value.
|
||||
* @param {String} value -
|
||||
* @param {String} valueType -
|
||||
*/
|
||||
parseMetaValue(value, valueType) {
|
||||
let parsedValue;
|
||||
|
||||
switch (valueType) {
|
||||
case 'integer':
|
||||
parsedValue = parseInt(value, 10);
|
||||
break;
|
||||
case 'float':
|
||||
parsedValue = parseFloat(value);
|
||||
break;
|
||||
case 'boolean':
|
||||
parsedValue = Boolean(value);
|
||||
break;
|
||||
case 'json':
|
||||
parsedValue = JSON.parse(parsedValue);
|
||||
break;
|
||||
default:
|
||||
parsedValue = value;
|
||||
break;
|
||||
}
|
||||
return parsedValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Format the metadata before saving to the database.
|
||||
* @param {String|Number|Boolean} value -
|
||||
* @param {String} valueType -
|
||||
* @return {String|Number|Boolean} -
|
||||
*/
|
||||
formatMetaValue(value, valueType) {
|
||||
let parsedValue;
|
||||
|
||||
switch (valueType) {
|
||||
case 'number':
|
||||
parsedValue = `${value}`;
|
||||
break;
|
||||
case 'boolean':
|
||||
parsedValue = value ? '1' : '0';
|
||||
break;
|
||||
case 'json':
|
||||
parsedValue = JSON.stringify(parsedValue);
|
||||
break;
|
||||
default:
|
||||
parsedValue = value;
|
||||
break;
|
||||
}
|
||||
return parsedValue;
|
||||
},
|
||||
|
||||
mapMetadata(attr, parseType = 'parse') {
|
||||
return {
|
||||
key: attr[this.KEY_COLUMN],
|
||||
value: (parseType === 'parse')
|
||||
? this.parseMetaValue(
|
||||
attr[this.VALUE_COLUMN],
|
||||
this.TYPE_COLUMN ? attr[this.TYPE_COLUMN] : false,
|
||||
)
|
||||
: this.formatMetaValue(
|
||||
attr[this.VALUE_COLUMN],
|
||||
this.TYPE_COLUMN ? attr[this.TYPE_COLUMN] : false,
|
||||
),
|
||||
...this.extraColumns.map((extraCol) => ({
|
||||
[extraCol]: attr[extraCol] || null,
|
||||
})),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse the metadata collection.
|
||||
* @param {Array} collection -
|
||||
*/
|
||||
mapMetadataCollection(collection, parseType = 'parse') {
|
||||
return collection.map((model) => this.mapMetadata(model.attributes, parseType));
|
||||
},
|
||||
};
|
||||
@@ -1,48 +1,43 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import { snakeCase, transform } from 'lodash';
|
||||
import { mapKeysDeep } from 'utils';
|
||||
import PaginationQueryBuilder from 'models/Pagination';
|
||||
import DateSession from 'models/DateSession';
|
||||
import { QueryBuilder, Model } from 'objection';
|
||||
|
||||
export default class ModelBase extends mixin(Model, [DateSession]) {
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
interface PaginationResult<M extends Model> {
|
||||
results: M[];
|
||||
pagination: {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
};
|
||||
}
|
||||
|
||||
static get knexBinded() {
|
||||
return this.knexBindInstance;
|
||||
}
|
||||
export type PaginationQueryBuilderType<M extends Model> = QueryBuilder<
|
||||
M,
|
||||
PaginationResult<M>
|
||||
>;
|
||||
|
||||
static set knexBinded(knex) {
|
||||
this.knexBindInstance = knex;
|
||||
}
|
||||
|
||||
static get collection() {
|
||||
return Array;
|
||||
}
|
||||
|
||||
static query(...args) {
|
||||
return super.query(...args).runAfter((result) => {
|
||||
if (Array.isArray(result)) {
|
||||
return this.collection.from(result);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
static get QueryBuilder() {
|
||||
return PaginationQueryBuilder;
|
||||
}
|
||||
|
||||
static relationBindKnex(model) {
|
||||
return this.knexBinded ? model.bindKnex(this.knexBinded) : model;
|
||||
}
|
||||
|
||||
static changeAmount(whereAttributes, attribute, amount, trx) {
|
||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||
|
||||
return this.query(trx)
|
||||
.where(whereAttributes)
|
||||
[changeMethod](attribute, Math.abs(amount));
|
||||
class PaginationQueryBuilder<M extends Model, R = M[]> extends QueryBuilder<
|
||||
M,
|
||||
R
|
||||
> {
|
||||
pagination(page: number, pageSize: number): PaginationQueryBuilderType<M> {
|
||||
const query = super.page(page, pageSize);
|
||||
|
||||
return query.runAfter(({ results, total }) => {
|
||||
return {
|
||||
results,
|
||||
pagination: {
|
||||
total,
|
||||
page: page + 1,
|
||||
pageSize,
|
||||
},
|
||||
};
|
||||
}) as unknown as PaginationQueryBuilderType<M>;
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseModel extends Model {
|
||||
public readonly id: number;
|
||||
public readonly tableName: string;
|
||||
|
||||
QueryBuilderType!: PaginationQueryBuilder<this>;
|
||||
static QueryBuilder = PaginationQueryBuilder;
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { IModelMeta, ISearchRole } from '@/interfaces';
|
||||
|
||||
export default (Model) =>
|
||||
class ModelSearchable extends Model {
|
||||
/**
|
||||
* Searchable model.
|
||||
*/
|
||||
static get searchable(): IModelMeta {
|
||||
throw true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search roles.
|
||||
*/
|
||||
static get searchRoles(): ISearchRole[] {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
import { get } from 'lodash';
|
||||
import {
|
||||
IModelMeta,
|
||||
IModelMetaField,
|
||||
IModelMetaDefaultSort,
|
||||
} from '@/interfaces';
|
||||
|
||||
const defaultModelMeta = {
|
||||
fields: {},
|
||||
fields2: {},
|
||||
};
|
||||
|
||||
export default (Model) =>
|
||||
class ModelSettings extends Model {
|
||||
/**
|
||||
*
|
||||
* @returns {IModelMeta}
|
||||
*/
|
||||
static get meta(): IModelMeta {
|
||||
throw new Error('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsed meta merged with default emta.
|
||||
* @returns {IModelMeta}
|
||||
*/
|
||||
static get parsedMeta(): IModelMeta {
|
||||
return {
|
||||
...defaultModelMeta,
|
||||
...this.meta,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve specific model field meta of the given field key.
|
||||
* @param {string} key
|
||||
* @returns {IModelMetaField}
|
||||
*/
|
||||
public static getField(key: string, attribute?: string): IModelMetaField {
|
||||
const field = get(this.meta.fields, key);
|
||||
|
||||
return attribute ? get(field, attribute) : field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the specific model meta.
|
||||
* @param {string} key
|
||||
* @returns
|
||||
*/
|
||||
public static getMeta(key?: string) {
|
||||
return key ? get(this.parsedMeta, key) : this.parsedMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model meta fields.
|
||||
* @return {{ [key: string]: IModelMetaField }}
|
||||
*/
|
||||
public static get fields(): { [key: string]: IModelMetaField } {
|
||||
return this.getMeta('fields');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model default sort settings.
|
||||
* @return {IModelMetaDefaultSort}
|
||||
*/
|
||||
public static get defaultSort(): IModelMetaDefaultSort {
|
||||
return this.getMeta('defaultSort');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default filter field key.
|
||||
* @return {string}
|
||||
*/
|
||||
public static get defaultFilterField(): string {
|
||||
return this.getMeta('defaultFilterField');
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import definedOptions from '@/data/options';
|
||||
|
||||
|
||||
export default class Option extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'options';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given options is defined or either not.
|
||||
* @param {Array} options
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static validateDefined(options) {
|
||||
const notDefined = [];
|
||||
|
||||
options.forEach((option) => {
|
||||
if (!definedOptions[option.group]) {
|
||||
notDefined.push(option);
|
||||
} else if (!definedOptions[option.group].some((o) => o.key === option.key)) {
|
||||
notDefined.push(option);
|
||||
}
|
||||
});
|
||||
return notDefined;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import { castArray, omit, pick } from 'lodash';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
|
||||
export default class PaginationQueryBuilder extends Model.QueryBuilder {
|
||||
pagination(page, pageSize) {
|
||||
return super.page(page, pageSize).runAfter(({ results, total }) => {
|
||||
return {
|
||||
results,
|
||||
pagination: {
|
||||
total,
|
||||
page: page + 1,
|
||||
pageSize,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
queryAndThrowIfHasRelations = ({
|
||||
type,
|
||||
message,
|
||||
excludeRelations = [],
|
||||
includedRelations = [],
|
||||
}) => {
|
||||
const _excludeRelations = castArray(excludeRelations);
|
||||
const _includedRelations = castArray(includedRelations);
|
||||
|
||||
const model = this.modelClass();
|
||||
const modelRelations = Object.keys(model.relationMappings).filter(
|
||||
(relation) =>
|
||||
[Model.HasManyRelation, Model.HasOneRelation].indexOf(
|
||||
model.relationMappings[relation]?.relation
|
||||
) !== -1
|
||||
);
|
||||
const relations = model.secureDeleteRelations || modelRelations;
|
||||
const filteredByIncluded = relations.filter((r) =>
|
||||
_includedRelations.includes(r)
|
||||
);
|
||||
const filteredByExcluded = relations.filter(
|
||||
(r) => !excludeRelations.includes(r)
|
||||
);
|
||||
const filteredRelations = !isEmpty(_includedRelations)
|
||||
? filteredByIncluded
|
||||
: !isEmpty(_excludeRelations)
|
||||
? filteredByExcluded
|
||||
: relations;
|
||||
|
||||
this.runAfter((model, query) => {
|
||||
const nonEmptyRelations = filteredRelations.filter(
|
||||
(relation) => !isEmpty(model[relation])
|
||||
);
|
||||
if (nonEmptyRelations.length > 0) {
|
||||
throw new ServiceError(type || 'MODEL_HAS_RELATIONS', { message });
|
||||
}
|
||||
return model;
|
||||
});
|
||||
return this.onBuild((query) => {
|
||||
filteredRelations.forEach((relation) => {
|
||||
query.withGraphFetched(`${relation}(selectId)`).modifiers({
|
||||
selectId(builder) {
|
||||
builder.select('id');
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export class PaymentIntegration extends Model {
|
||||
paymentEnabled!: boolean;
|
||||
payoutEnabled!: boolean;
|
||||
|
||||
static get tableName() {
|
||||
return 'payment_integrations';
|
||||
}
|
||||
|
||||
static get idColumn() {
|
||||
return 'id';
|
||||
}
|
||||
|
||||
static get virtualAttributes() {
|
||||
return ['fullEnabled'];
|
||||
}
|
||||
|
||||
static get jsonAttributes() {
|
||||
return ['options'];
|
||||
}
|
||||
|
||||
get fullEnabled() {
|
||||
return this.paymentEnabled && this.payoutEnabled;
|
||||
}
|
||||
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Query to filter enabled payment and payout.
|
||||
*/
|
||||
fullEnabled(query) {
|
||||
query.where('paymentEnabled', true).andWhere('payoutEnabled', true);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get jsonSchema() {
|
||||
return {
|
||||
type: 'object',
|
||||
required: ['name', 'service'],
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
service: { type: 'string' },
|
||||
paymentEnabled: { type: 'boolean' },
|
||||
payoutEnabled: { type: 'boolean' },
|
||||
accountId: { type: 'string' },
|
||||
options: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
bankAccountId: { type: 'number' },
|
||||
clearingAccountId: { type: 'number' },
|
||||
},
|
||||
},
|
||||
createdAt: { type: 'string', format: 'date-time' },
|
||||
updatedAt: { type: 'string', format: 'date-time' },
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
import { Features } from '@/interfaces';
|
||||
|
||||
export default {
|
||||
importable: true,
|
||||
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'paymentReceiveNo',
|
||||
fields: {
|
||||
customer: {
|
||||
name: 'payment_receive.field.customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
payment_date: {
|
||||
name: 'payment_receive.field.payment_date',
|
||||
column: 'payment_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
amount: {
|
||||
name: 'payment_receive.field.amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'payment_receive.field.reference_no',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
deposit_account: {
|
||||
name: 'payment_receive.field.deposit_account',
|
||||
column: 'deposit_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'depositAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
payment_receive_no: {
|
||||
name: 'payment_receive.field.payment_receive_no',
|
||||
column: 'payment_receive_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
statement: {
|
||||
name: 'payment_receive.field.statement',
|
||||
column: 'statement',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'payment_receive.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldDate: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
customer: {
|
||||
name: 'payment_receive.field.customer',
|
||||
accessor: 'customer.displayName',
|
||||
type: 'text',
|
||||
},
|
||||
paymentDate: {
|
||||
name: 'payment_receive.field.payment_date',
|
||||
type: 'date',
|
||||
accessor: 'formattedPaymentDate',
|
||||
},
|
||||
amount: {
|
||||
name: 'payment_receive.field.amount',
|
||||
type: 'number',
|
||||
accessor: 'formattedAmount',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'payment_receive.field.reference_no',
|
||||
type: 'text',
|
||||
},
|
||||
depositAccount: {
|
||||
name: 'payment_receive.field.deposit_account',
|
||||
accessor: 'depositAccount.name',
|
||||
type: 'text',
|
||||
},
|
||||
paymentReceiveNo: {
|
||||
name: 'payment_receive.field.payment_receive_no',
|
||||
type: 'text',
|
||||
},
|
||||
statement: {
|
||||
name: 'payment_receive.field.statement',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
accessor: 'entries',
|
||||
type: 'collection',
|
||||
collectionOf: 'object',
|
||||
columns: {
|
||||
date: {
|
||||
name: 'Invoice date',
|
||||
accessor: 'invoice.invoiceDateFormatted',
|
||||
},
|
||||
invoiceNo: {
|
||||
name: 'Invoice No.',
|
||||
accessor: 'invoice.invoiceNo',
|
||||
},
|
||||
invoiceRefNo: {
|
||||
name: 'Invoice Reference No.',
|
||||
accessor: 'invoice.referenceNo',
|
||||
},
|
||||
invoiceAmount: {
|
||||
name: 'Invoice Amount',
|
||||
accessor: 'invoice.totalFormatted',
|
||||
},
|
||||
paidAmount: {
|
||||
name: 'Paid Amount',
|
||||
accessor: 'paymentAmountFormatted',
|
||||
},
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
name: 'payment_receive.field.created_at',
|
||||
type: 'date',
|
||||
printable: false,
|
||||
},
|
||||
branch: {
|
||||
name: 'Branch',
|
||||
type: 'text',
|
||||
accessor: 'branch.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
customerId: {
|
||||
name: 'payment_receive.field.customer',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Contact',
|
||||
relationImportMatch: ['displayName'],
|
||||
required: true,
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'payment_receive.field.exchange_rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
paymentDate: {
|
||||
name: 'payment_receive.field.payment_date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'payment_receive.field.reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
depositAccountId: {
|
||||
name: 'payment_receive.field.deposit_account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: 'Matches the account name or code.',
|
||||
},
|
||||
paymentReceiveNo: {
|
||||
name: 'payment_receive.field.payment_receive_no',
|
||||
fieldType: 'text',
|
||||
importHint: 'The payment number should be unique.',
|
||||
},
|
||||
statement: {
|
||||
name: 'payment_receive.field.statement',
|
||||
fieldType: 'text',
|
||||
},
|
||||
entries: {
|
||||
name: 'payment_receive.field.entries',
|
||||
fieldType: 'collection',
|
||||
collectionOf: 'object',
|
||||
collectionMinLength: 1,
|
||||
required: true,
|
||||
fields: {
|
||||
invoiceId: {
|
||||
name: 'payment_receive.field.invoice',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'SaleInvoice',
|
||||
relationImportMatch: 'invoiceNo',
|
||||
required: true,
|
||||
importHint: 'Matches the invoice number.',
|
||||
},
|
||||
paymentAmount: {
|
||||
name: 'payment_receive.field.entries.payment_amount',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
branchId: {
|
||||
name: 'Branch',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Branch',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.BRANCHES],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,193 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import PaymentReceiveSettings from './PaymentReceive.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class PaymentReceive extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
amount!: number;
|
||||
paymentAmount!: number;
|
||||
exchangeRate!: number;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'payment_receives';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['localAmount', 'total'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Payment receive amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Payment receive total.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.paymentAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resourcable model.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const Customer = require('models/Customer');
|
||||
const Account = require('models/Account');
|
||||
const Branch = require('models/Branch');
|
||||
const Document = require('models/Document');
|
||||
const { PdfTemplate } = require('models/PdfTemplate');
|
||||
|
||||
return {
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Customer.default,
|
||||
join: {
|
||||
from: 'payment_receives.customerId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.where('contact_service', 'customer');
|
||||
},
|
||||
},
|
||||
depositAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'payment_receives.depositAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: PaymentReceiveEntry.default,
|
||||
join: {
|
||||
from: 'payment_receives.id',
|
||||
to: 'payment_receives_entries.paymentReceiveId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'payment_receives.id',
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter: (builder) => {
|
||||
builder.where('reference_type', 'PaymentReceive');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Payment receive may belongs to branch.
|
||||
*/
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch.default,
|
||||
join: {
|
||||
from: 'payment_receives.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Payment transaction may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'payment_receives.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'PaymentReceive');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Payment received may belongs to pdf branding template.
|
||||
*/
|
||||
pdfTemplate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PdfTemplate,
|
||||
join: {
|
||||
from: 'payment_receives.pdfTemplateId',
|
||||
to: 'pdf_templates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static get meta() {
|
||||
return PaymentReceiveSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'payment_receive_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class PaymentReceiveEntry extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'payment_receives_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const PaymentReceive = require('models/PaymentReceive');
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
|
||||
return {
|
||||
/**
|
||||
*/
|
||||
payment: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PaymentReceive.default,
|
||||
join: {
|
||||
from: 'payment_receives_entries.paymentReceiveId',
|
||||
to: 'payment_receives.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The payment receive entry have have sale invoice.
|
||||
*/
|
||||
invoice: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'payment_receives_entries.invoiceId',
|
||||
to: 'sales_invoices.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import { getUploadedObjectUri } from '@/services/Attachments/utils';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export class PdfTemplate extends TenantModel {
|
||||
public readonly attributes: Record<string, any>;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'pdf_templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Json schema.
|
||||
*/
|
||||
static get jsonSchema() {
|
||||
return {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
templateName: { type: 'string' },
|
||||
attributes: { type: 'object' }, // JSON field definition
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters the due invoices.
|
||||
*/
|
||||
default(query) {
|
||||
query.where('default', true);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['companyLogoUri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the company logo uri.
|
||||
* @returns {string}
|
||||
*/
|
||||
get companyLogoUri() {
|
||||
return this.attributes?.companyLogoKey
|
||||
? getUploadedObjectUri(this.attributes.companyLogoKey)
|
||||
: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class PlaidItem extends TenantModel {
|
||||
pausedAt: Date;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'plaid_items';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['isPaused'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the Plaid item feeds syncing is paused.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPaused() {
|
||||
return !!this.pausedAt;
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
import { mixin, Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class Project extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
costEstimate!: number;
|
||||
deadline!: Date;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'projects';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
totalExpensesDetails(builder) {
|
||||
builder
|
||||
.withGraphFetched('expenses')
|
||||
.modifyGraph('expenses', (builder) => {
|
||||
builder.select(['projectId']);
|
||||
builder.groupBy('projectId');
|
||||
|
||||
builder.sum('totalAmount as totalExpenses');
|
||||
builder.sum('invoicedAmount as totalInvoicedExpenses');
|
||||
});
|
||||
},
|
||||
|
||||
totalBillsDetails(builder) {
|
||||
builder.withGraphFetched('tasks').modifyGraph('tasks', (builder) => {
|
||||
builder.select(['projectId']);
|
||||
builder.groupBy('projectId');
|
||||
|
||||
builder.modify('sumTotalActualHours');
|
||||
builder.modify('sumTotalEstimateHours');
|
||||
builder.modify('sumTotalInvoicedHours');
|
||||
|
||||
builder.modify('sumTotalActualAmount');
|
||||
builder.modify('sumTotalInvoicedAmount');
|
||||
builder.modify('sumTotalEstimateAmount');
|
||||
});
|
||||
},
|
||||
|
||||
totalTasksDetails(builder) {
|
||||
builder.withGraphFetched('tasks').modifyGraph('tasks', (builder) => {
|
||||
builder.select(['projectId']);
|
||||
builder.groupBy('projectId');
|
||||
|
||||
builder.modify('sumTotalActualHours');
|
||||
builder.modify('sumTotalEstimateHours');
|
||||
builder.modify('sumTotalInvoicedHours');
|
||||
|
||||
builder.modify('sumTotalActualAmount');
|
||||
builder.modify('sumTotalInvoicedAmount');
|
||||
builder.modify('sumTotalEstimateAmount');
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Contact = require('models/Contact');
|
||||
const Task = require('models/Task');
|
||||
const Time = require('models/Time');
|
||||
const Expense = require('models/Expense');
|
||||
const Bill = require('models/Bill');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Belongs to customer model.
|
||||
*/
|
||||
contact: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact.default,
|
||||
join: {
|
||||
from: 'projects.contactId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Project may has many associated tasks.
|
||||
*/
|
||||
tasks: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Task.default,
|
||||
join: {
|
||||
from: 'projects.id',
|
||||
to: 'tasks.projectId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Project may has many associated times.
|
||||
*/
|
||||
times: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Time.default,
|
||||
join: {
|
||||
from: 'projects.id',
|
||||
to: 'times.projectId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Project may has many associated expenses.
|
||||
*/
|
||||
expenses: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Expense.default,
|
||||
join: {
|
||||
from: 'projects.id',
|
||||
to: 'expenses_transactions.projectId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Project may has many associated bills.
|
||||
*/
|
||||
bills: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Bill.default,
|
||||
join: {
|
||||
from: 'projects.id',
|
||||
to: 'bills.projectId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { mixin, Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class ProjectItemEntryRef extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'projects_item_entries_links';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
|
||||
return {
|
||||
itemEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'projects_item_entries_links.itemEntryId',
|
||||
to: 'items_entries.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { Model } from 'objection';
|
||||
|
||||
export class RecognizedBankTransaction extends TenantModel {
|
||||
public bankRuleId!: number;
|
||||
public uncategorizedTransactionId!: number;
|
||||
public assignedCategory!: string;
|
||||
public assignedAccountId!: number;
|
||||
public assignedPayee!: string;
|
||||
public assignedMemo!: string;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'recognized_bank_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const UncategorizedCashflowTransaction = require('./UncategorizedCashflowTransaction');
|
||||
const Account = require('./Account');
|
||||
const { BankRule } = require('./BankRule');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Recognized bank transaction may belongs to uncategorized transactions.
|
||||
*/
|
||||
uncategorizedTransactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: UncategorizedCashflowTransaction.default,
|
||||
join: {
|
||||
from: 'recognized_bank_transactions.uncategorizedTransactionId',
|
||||
to: 'uncategorized_cashflow_transactions.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Recognized bank transaction may belongs to assign account.
|
||||
*/
|
||||
assignAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'recognized_bank_transactions.assignedAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Recognized bank transaction may belongs to bank rule.
|
||||
*/
|
||||
bankRule: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: BankRule,
|
||||
join: {
|
||||
from: 'recognized_bank_transactions.bankRuleId',
|
||||
to: 'bank_rules.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class RefundCreditNote extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'refund_credit_note_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Account = require('models/Account');
|
||||
const CreditNote = require('models/CreditNote');
|
||||
|
||||
return {
|
||||
fromAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'refund_credit_note_transactions.fromAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
creditNote: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: CreditNote.default,
|
||||
join: {
|
||||
from: 'refund_credit_note_transactions.creditNoteId',
|
||||
to: 'credit_notes.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class RefundVendorCredit extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'refund_vendor_credit_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const VendorCredit = require('models/VendorCredit');
|
||||
const Account = require('models/Account');
|
||||
|
||||
return {
|
||||
depositAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'refund_vendor_credit_transactions.depositAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
vendorCredit: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: VendorCredit.default,
|
||||
join: {
|
||||
from: 'refund_vendor_credit_transactions.vendorCreditId',
|
||||
to: 'vendor_credits.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
|
||||
export default class ResourceableModel {
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
|
||||
export default class Role extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'roles';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const RolePermission = require('models/RolePermission');
|
||||
|
||||
return {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
permissions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: RolePermission.default,
|
||||
join: {
|
||||
from: 'roles.id',
|
||||
to: 'role_permissions.roleId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class RolePermission extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'role_permissions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Role = require('models/Role');
|
||||
|
||||
return {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
role: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Role.default,
|
||||
join: {
|
||||
from: 'role_permissions.roleId',
|
||||
to: 'roles.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
import { Features } from '@/interfaces';
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'estimate_date',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'estimate_date',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'estimateNumber',
|
||||
|
||||
print: {
|
||||
pageTitle: 'Sale Estimates',
|
||||
},
|
||||
|
||||
fields: {
|
||||
amount: {
|
||||
name: 'estimate.field.amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
estimate_number: {
|
||||
name: 'estimate.field.estimate_number',
|
||||
column: 'estimate_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
customer: {
|
||||
name: 'estimate.field.customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
estimate_date: {
|
||||
name: 'estimate.field.estimate_date',
|
||||
column: 'estimate_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
expiration_date: {
|
||||
name: 'estimate.field.expiration_date',
|
||||
column: 'expiration_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'estimate.field.reference_no',
|
||||
column: 'reference',
|
||||
fieldType: 'text',
|
||||
},
|
||||
note: {
|
||||
name: 'estimate.field.note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
terms_conditions: {
|
||||
name: 'estimate.field.terms_conditions',
|
||||
column: 'terms_conditions',
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
name: 'estimate.field.status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ label: 'estimate.field.status.delivered', key: 'delivered' },
|
||||
{ label: 'estimate.field.status.rejected', key: 'rejected' },
|
||||
{ label: 'estimate.field.status.approved', key: 'approved' },
|
||||
{ label: 'estimate.field.status.draft', key: 'draft' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
created_at: {
|
||||
name: 'estimate.field.created_at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
customer: {
|
||||
name: 'Customer',
|
||||
type: 'text',
|
||||
accessor: 'customer.displayName',
|
||||
exportable: true,
|
||||
},
|
||||
estimateDate: {
|
||||
name: 'Estimate Date',
|
||||
type: 'date',
|
||||
accessor: 'formattedEstimateDate',
|
||||
exportable: true,
|
||||
},
|
||||
expirationDate: {
|
||||
name: 'Expiration Date',
|
||||
type: 'date',
|
||||
accessor: 'formattedExpirationDate',
|
||||
exportable: true,
|
||||
},
|
||||
estimateNumber: {
|
||||
name: 'Estimate No.',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
reference: {
|
||||
name: 'Reference No.',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
amount: {
|
||||
name: 'Amount',
|
||||
accessor: 'formattedAmount',
|
||||
type: 'text',
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'Exchange Rate',
|
||||
type: 'number',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'Currency',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
termsConditions: {
|
||||
name: 'Terms & Conditions',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
delivered: {
|
||||
name: 'Delivered',
|
||||
type: 'boolean',
|
||||
accessor: 'isDelivered',
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
accessor: 'entries',
|
||||
type: 'collection',
|
||||
collectionOf: 'object',
|
||||
columns: {
|
||||
itemName: {
|
||||
name: 'Item Name',
|
||||
accessor: 'item.name',
|
||||
},
|
||||
rate: {
|
||||
name: 'Item Rate',
|
||||
accessor: 'rateFormatted',
|
||||
},
|
||||
quantity: {
|
||||
name: 'Item Quantity',
|
||||
accessor: 'quantityFormatted',
|
||||
},
|
||||
description: {
|
||||
name: 'Item Description',
|
||||
printable: false,
|
||||
},
|
||||
amount: {
|
||||
name: 'Item Amount',
|
||||
accessor: 'totalFormatted',
|
||||
},
|
||||
},
|
||||
},
|
||||
branch: {
|
||||
name: 'Branch',
|
||||
type: 'text',
|
||||
accessor: 'branch.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
warehouse: {
|
||||
name: 'Warehouse',
|
||||
type: 'text',
|
||||
accessor: 'warehouse.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
customerId: {
|
||||
name: 'Customer',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Contact',
|
||||
relationImportMatch: ['displayName'],
|
||||
required: true,
|
||||
},
|
||||
estimateDate: {
|
||||
name: 'Estimate Date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
expirationDate: {
|
||||
name: 'Expiration Date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
estimateNumber: {
|
||||
name: 'Estimate No.',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference: {
|
||||
name: 'Reference No.',
|
||||
fieldType: 'text',
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'Exchange Rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'Currency',
|
||||
fieldType: 'text',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
termsConditions: {
|
||||
name: 'Terms & Conditions',
|
||||
fieldType: 'text',
|
||||
},
|
||||
delivered: {
|
||||
name: 'Delivered',
|
||||
type: 'boolean',
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
fieldType: 'collection',
|
||||
collectionOf: 'object',
|
||||
collectionMinLength: 1,
|
||||
required: true,
|
||||
fields: {
|
||||
itemId: {
|
||||
name: 'invoice.field.item_name',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'invoice.field.rate',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
quantity: {
|
||||
name: 'invoice.field.quantity',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
name: 'Line Description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
branchId: {
|
||||
name: 'Branch',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Branch',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.BRANCHES],
|
||||
required: true,
|
||||
},
|
||||
warehouseId: {
|
||||
name: 'Warehouse',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Warehouse',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.WAREHOUSES],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('orderByStatus', role.order);
|
||||
}
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
import moment from 'moment';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { defaultToTransform } from 'utils';
|
||||
import SaleEstimateSettings from './SaleEstimate.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/Estimates/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
import { defaultTo } from 'lodash';
|
||||
|
||||
export default class SaleEstimate extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public exchangeRate: number;
|
||||
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
|
||||
public adjustment: number;
|
||||
|
||||
public expirationDate!: string;
|
||||
public deliveredAt!: string | null;
|
||||
public approvedAt!: string | null;
|
||||
public rejectedAt!: string | null;
|
||||
|
||||
public convertedToInvoiceId!: number | null;
|
||||
public convertedToInvoiceAt!: string | null;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'sales_estimates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'localAmount',
|
||||
'discountAmount',
|
||||
'discountPercentage',
|
||||
'total',
|
||||
'totalLocal',
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
'isDelivered',
|
||||
'isExpired',
|
||||
'isConvertedToInvoice',
|
||||
'isApproved',
|
||||
'isRejected',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate subtotal in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.localAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage
|
||||
? this.discount
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate total.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
const adjustmentAmount = defaultTo(this.adjustment, 0);
|
||||
|
||||
return this.subtotal - this.discountAmount + adjustmentAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate total in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the sale estimate converted to sale invoice.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isConvertedToInvoice() {
|
||||
return !!(this.convertedToInvoiceId && this.convertedToInvoiceAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the estimate is delivered.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isDelivered() {
|
||||
return !!this.deliveredAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the estimate is expired.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isExpired() {
|
||||
return defaultToTransform(
|
||||
this.expirationDate,
|
||||
moment().isAfter(this.expirationDate, 'day'),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the estimate is approved.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isApproved() {
|
||||
return !!this.approvedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the estimate is reject.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isRejected() {
|
||||
return !!this.rejectedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to mark model as resourceable to viewable and filterable.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters the drafted estimates transactions.
|
||||
*/
|
||||
draft(query) {
|
||||
query.where('delivered_at', null);
|
||||
},
|
||||
/**
|
||||
* Filters the delivered estimates transactions.
|
||||
*/
|
||||
delivered(query) {
|
||||
query.whereNot('delivered_at', null);
|
||||
},
|
||||
/**
|
||||
* Filters the expired estimates transactions.
|
||||
*/
|
||||
expired(query) {
|
||||
query.where('expiration_date', '<', moment().format('YYYY-MM-DD'));
|
||||
},
|
||||
/**
|
||||
* Filters the rejected estimates transactions.
|
||||
*/
|
||||
rejected(query) {
|
||||
query.whereNot('rejected_at', null);
|
||||
},
|
||||
/**
|
||||
* Filters the invoiced estimates transactions.
|
||||
*/
|
||||
invoiced(query) {
|
||||
query.whereNot('converted_to_invoice_at', null);
|
||||
},
|
||||
/**
|
||||
* Filters the approved estimates transactions.
|
||||
*/
|
||||
approved(query) {
|
||||
query.whereNot('approved_at', null);
|
||||
},
|
||||
/**
|
||||
* Sorting the estimates orders by delivery status.
|
||||
*/
|
||||
orderByStatus(query, order) {
|
||||
query.orderByRaw(`delivered_at is null ${order}`);
|
||||
},
|
||||
/**
|
||||
* Filtering the estimates oreders by status field.
|
||||
*/
|
||||
filterByStatus(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'delivered':
|
||||
query.modify('delivered');
|
||||
break;
|
||||
case 'approved':
|
||||
query.modify('approved');
|
||||
break;
|
||||
case 'rejected':
|
||||
query.modify('rejected');
|
||||
break;
|
||||
case 'invoiced':
|
||||
query.modify('invoiced');
|
||||
break;
|
||||
case 'expired':
|
||||
query.modify('expired');
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const Customer = require('models/Customer');
|
||||
const Branch = require('models/Branch');
|
||||
const Warehouse = require('models/Warehouse');
|
||||
const Document = require('models/Document');
|
||||
const { PdfTemplate } = require('models/PdfTemplate');
|
||||
|
||||
return {
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Customer.default,
|
||||
join: {
|
||||
from: 'sales_estimates.customerId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'customer');
|
||||
},
|
||||
},
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'sales_estimates.id',
|
||||
to: 'items_entries.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'SaleEstimate');
|
||||
builder.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale estimate may belongs to branch.
|
||||
*/
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch.default,
|
||||
join: {
|
||||
from: 'sales_estimates.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale estimate may has associated warehouse.
|
||||
*/
|
||||
warehouse: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Warehouse.default,
|
||||
join: {
|
||||
from: 'sales_estimates.warehouseId',
|
||||
to: 'warehouses.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale estimate transaction may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'sales_estimates.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'SaleEstimate');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale estimate may belongs to pdf branding template.
|
||||
*/
|
||||
pdfTemplate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PdfTemplate,
|
||||
join: {
|
||||
from: 'sales_estimates.pdfTemplateId',
|
||||
to: 'pdf_templates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return SaleEstimateSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search roles.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'amount', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'estimate_number', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
|
||||
export default class SaleEstimateEntry extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'sales_estimate_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const SaleEstimate = require('models/SaleEstimate');
|
||||
|
||||
return {
|
||||
estimate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleEstimate.default,
|
||||
join: {
|
||||
from: 'sales_estimates.id',
|
||||
to: 'sales_estimate_entries.estimate_id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
import { Features } from '@/interfaces';
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'customer',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'invoiceNo',
|
||||
|
||||
print: {
|
||||
pageTitle: 'Sale invoices',
|
||||
},
|
||||
fields: {
|
||||
customer: {
|
||||
name: 'invoice.field.customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
invoice_date: {
|
||||
name: 'invoice.field.invoice_date',
|
||||
column: 'invoice_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
due_date: {
|
||||
name: 'invoice.field.due_date',
|
||||
column: 'due_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
invoice_no: {
|
||||
name: 'invoice.field.invoice_no',
|
||||
column: 'invoice_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'invoice.field.reference_no',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
invoice_message: {
|
||||
name: 'invoice.field.invoice_message',
|
||||
column: 'invoice_message',
|
||||
fieldType: 'text',
|
||||
},
|
||||
terms_conditions: {
|
||||
name: 'invoice.field.terms_conditions',
|
||||
column: 'terms_conditions',
|
||||
fieldType: 'text',
|
||||
},
|
||||
amount: {
|
||||
name: 'invoice.field.amount',
|
||||
column: 'balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
payment_amount: {
|
||||
name: 'invoice.field.payment_amount',
|
||||
column: 'payment_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
due_amount: {
|
||||
// calculated.
|
||||
name: 'invoice.field.due_amount',
|
||||
column: 'due_amount',
|
||||
fieldType: 'number',
|
||||
virtualColumn: true,
|
||||
},
|
||||
status: {
|
||||
name: 'invoice.field.status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', label: 'invoice.field.status.draft' },
|
||||
{ key: 'delivered', label: 'invoice.field.status.delivered' },
|
||||
{ key: 'unpaid', label: 'invoice.field.status.unpaid' },
|
||||
{ key: 'overdue', label: 'invoice.field.status.overdue' },
|
||||
{ key: 'partially-paid', label: 'invoice.field.status.partially-paid' },
|
||||
{ key: 'paid', label: 'invoice.field.status.paid' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
created_at: {
|
||||
name: 'invoice.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
invoiceDate: {
|
||||
name: 'invoice.field.invoice_date',
|
||||
type: 'date',
|
||||
accessor: 'invoiceDateFormatted',
|
||||
},
|
||||
dueDate: {
|
||||
name: 'invoice.field.due_date',
|
||||
type: 'date',
|
||||
accessor: 'dueDateFormatted',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'invoice.field.reference_no',
|
||||
type: 'text',
|
||||
},
|
||||
invoiceNo: {
|
||||
name: 'invoice.field.invoice_no',
|
||||
type: 'text',
|
||||
},
|
||||
customer: {
|
||||
name: 'invoice.field.customer',
|
||||
type: 'text',
|
||||
accessor: 'customer.displayName',
|
||||
},
|
||||
amount: {
|
||||
name: 'invoice.field.amount',
|
||||
type: 'text',
|
||||
accessor: 'balanceAmountFormatted',
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'invoice.field.exchange_rate',
|
||||
type: 'number',
|
||||
printable: false,
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'invoice.field.currency',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
paidAmount: {
|
||||
name: 'Paid Amount',
|
||||
accessor: 'paymentAmountFormatted',
|
||||
},
|
||||
dueAmount: {
|
||||
name: 'Due Amount',
|
||||
accessor: 'dueAmountFormatted',
|
||||
},
|
||||
invoiceMessage: {
|
||||
name: 'invoice.field.invoice_message',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
termsConditions: {
|
||||
name: 'invoice.field.terms_conditions',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
delivered: {
|
||||
name: 'invoice.field.delivered',
|
||||
type: 'boolean',
|
||||
printable: false,
|
||||
accessor: 'isDelivered',
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
accessor: 'entries',
|
||||
type: 'collection',
|
||||
collectionOf: 'object',
|
||||
columns: {
|
||||
itemName: {
|
||||
name: 'Item Name',
|
||||
accessor: 'item.name',
|
||||
},
|
||||
rate: {
|
||||
name: 'Item Rate',
|
||||
accessor: 'rateFormatted',
|
||||
},
|
||||
quantity: {
|
||||
name: 'Item Quantity',
|
||||
accessor: 'quantityFormatted',
|
||||
},
|
||||
description: {
|
||||
name: 'Item Description',
|
||||
printable: false,
|
||||
},
|
||||
amount: {
|
||||
name: 'Item Amount',
|
||||
accessor: 'totalFormatted',
|
||||
},
|
||||
},
|
||||
},
|
||||
branch: {
|
||||
name: 'Branch',
|
||||
type: 'text',
|
||||
accessor: 'branch.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
warehouse: {
|
||||
name: 'Warehouse',
|
||||
type: 'text',
|
||||
accessor: 'warehouse.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
invoiceDate: {
|
||||
name: 'invoice.field.invoice_date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
dueDate: {
|
||||
name: 'invoice.field.due_date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'invoice.field.reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
invoiceNo: {
|
||||
name: 'invoice.field.invoice_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
customerId: {
|
||||
name: 'invoice.field.customer',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Contact',
|
||||
relationImportMatch: 'displayName',
|
||||
required: true,
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'invoice.field.exchange_rate',
|
||||
fieldType: 'number',
|
||||
printable: false,
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'invoice.field.currency',
|
||||
fieldType: 'text',
|
||||
printable: false,
|
||||
},
|
||||
invoiceMessage: {
|
||||
name: 'invoice.field.invoice_message',
|
||||
fieldType: 'text',
|
||||
printable: false,
|
||||
},
|
||||
termsConditions: {
|
||||
name: 'invoice.field.terms_conditions',
|
||||
fieldType: 'text',
|
||||
printable: false,
|
||||
},
|
||||
entries: {
|
||||
name: 'invoice.field.entries',
|
||||
fieldType: 'collection',
|
||||
collectionOf: 'object',
|
||||
collectionMinLength: 1,
|
||||
required: true,
|
||||
fields: {
|
||||
itemId: {
|
||||
name: 'invoice.field.item_name',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'invoice.field.rate',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
quantity: {
|
||||
name: 'invoice.field.quantity',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
name: 'invoice.field.description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
delivered: {
|
||||
name: 'invoice.field.delivered',
|
||||
fieldType: 'boolean',
|
||||
printable: false,
|
||||
},
|
||||
branchId: {
|
||||
name: 'Branch',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Branch',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.BRANCHES],
|
||||
required: true,
|
||||
},
|
||||
warehouseId: {
|
||||
name: 'Warehouse',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Warehouse',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.WAREHOUSES],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Status field filter custom query.
|
||||
*/
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('statusFilter', role.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status field sort custom query.
|
||||
*/
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,716 +0,0 @@
|
||||
import { mixin, Model, raw } from 'objection';
|
||||
import * as R from 'ramda';
|
||||
import { castArray, defaultTo, takeWhile } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import SaleInvoiceMeta from './SaleInvoice.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/Invoices/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
|
||||
export default class SaleInvoice extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public taxAmountWithheld: number;
|
||||
public balance: number;
|
||||
public paymentAmount: number;
|
||||
public exchangeRate: number;
|
||||
public writtenoffAmount: number;
|
||||
public creditedAmount: number;
|
||||
public isInclusiveTax: boolean;
|
||||
public writtenoffAt: Date;
|
||||
public dueDate: Date;
|
||||
public deliveredAt: Date;
|
||||
public pdfTemplateId: number;
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
public adjustment: number | null;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'sales_invoices';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
get pluralName() {
|
||||
return 'asdfsdf';
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'isDelivered',
|
||||
'isOverdue',
|
||||
'isPartiallyPaid',
|
||||
'isFullyPaid',
|
||||
'isWrittenoff',
|
||||
'isPaid',
|
||||
|
||||
'dueAmount',
|
||||
'balanceAmount',
|
||||
'remainingDays',
|
||||
'overdueDays',
|
||||
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
'subtotalExludingTax',
|
||||
|
||||
'taxAmountWithheldLocal',
|
||||
'discountAmount',
|
||||
'discountAmountLocal',
|
||||
'discountPercentage',
|
||||
|
||||
'total',
|
||||
'totalLocal',
|
||||
|
||||
'writtenoffAmountLocal',
|
||||
'adjustmentLocal',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice amount.
|
||||
* @todo Sugger attribute to balance, we need to rename the balance to amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get amount() {
|
||||
return this.balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice amount in base currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get amountLocal() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal. (Tax inclusive) if the tax inclusive is enabled.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal in base currency. (Tax inclusive) if the tax inclusive is enabled.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.amountLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sale invoice amount excluding tax.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalExludingTax() {
|
||||
return this.isInclusiveTax
|
||||
? this.subtotal - this.taxAmountWithheld
|
||||
: this.subtotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tax amount withheld in base currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get taxAmountWithheldLocal() {
|
||||
return this.taxAmountWithheld * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local discount amount.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountAmountLocal() {
|
||||
return this.discountAmount ? this.discountAmount * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage ? this.discount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjustment amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get adjustmentLocal(): number | null {
|
||||
return this.adjustment ? this.adjustment * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice total. (Tax included)
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
const adjustmentAmount = defaultTo(this.adjustment, 0);
|
||||
|
||||
return R.compose(
|
||||
R.add(adjustmentAmount),
|
||||
R.subtract(R.__, this.discountAmount),
|
||||
R.when(R.always(this.isInclusiveTax), R.add(this.taxAmountWithheld))
|
||||
)(this.subtotal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice total in local currency. (Tax included)
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the invoice is delivered.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isDelivered() {
|
||||
return !!this.deliveredAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the due date is over.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isOverdue() {
|
||||
return this.overdueDays > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sale invoice balance.
|
||||
* @return {number}
|
||||
*/
|
||||
get balanceAmount() {
|
||||
return this.paymentAmount + this.writtenoffAmount + this.creditedAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the invoice due amount.
|
||||
* Equation (Invoice amount - payment amount = Due amount)
|
||||
* @return {boolean}
|
||||
*/
|
||||
get dueAmount() {
|
||||
return Math.max(this.total - this.balanceAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmine whether the invoice paid partially.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPartiallyPaid() {
|
||||
return this.dueAmount !== this.total && this.dueAmount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deetarmine whether the invoice paid fully.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isFullyPaid() {
|
||||
return this.dueAmount === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the invoice paid fully or partially.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPaid() {
|
||||
return this.isPartiallyPaid || this.isFullyPaid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the sale invoice is written-off.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isWrittenoff() {
|
||||
return Boolean(this.writtenoffAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the remaining days in number
|
||||
* @return {number|null}
|
||||
*/
|
||||
get remainingDays() {
|
||||
const dateMoment = moment();
|
||||
const dueDateMoment = moment(this.dueDate);
|
||||
|
||||
return Math.max(dueDateMoment.diff(dateMoment, 'days'), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the overdue days in number.
|
||||
* @return {number|null}
|
||||
*/
|
||||
get overdueDays() {
|
||||
const dateMoment = moment();
|
||||
const dueDateMoment = moment(this.dueDate);
|
||||
|
||||
return Math.max(dateMoment.diff(dueDateMoment, 'days'), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters the due invoices.
|
||||
*/
|
||||
dueInvoices(query) {
|
||||
query.where(
|
||||
raw(`
|
||||
COALESCE(BALANCE, 0) -
|
||||
COALESCE(PAYMENT_AMOUNT, 0) -
|
||||
COALESCE(WRITTENOFF_AMOUNT, 0) -
|
||||
COALESCE(CREDITED_AMOUNT, 0) > 0
|
||||
`)
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Filters the invoices between the given date range.
|
||||
*/
|
||||
filterDateRange(query, startDate, endDate, type = 'day') {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const fromDate = moment(startDate).startOf(type).format(dateFormat);
|
||||
const toDate = moment(endDate).endOf(type).format(dateFormat);
|
||||
|
||||
if (startDate) {
|
||||
query.where('invoice_date', '>=', fromDate);
|
||||
}
|
||||
if (endDate) {
|
||||
query.where('invoice_date', '<=', toDate);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Filters the invoices in draft status.
|
||||
*/
|
||||
draft(query) {
|
||||
query.where('delivered_at', null);
|
||||
},
|
||||
/**
|
||||
* Filters the published invoices.
|
||||
*/
|
||||
published(query) {
|
||||
query.whereNot('delivered_at', null);
|
||||
},
|
||||
/**
|
||||
* Filters the delivered invoices.
|
||||
*/
|
||||
delivered(query) {
|
||||
query.whereNot('delivered_at', null);
|
||||
},
|
||||
/**
|
||||
* Filters the unpaid invoices.
|
||||
*/
|
||||
unpaid(query) {
|
||||
query.where(raw('PAYMENT_AMOUNT = 0'));
|
||||
},
|
||||
/**
|
||||
* Filters the overdue invoices.
|
||||
*/
|
||||
overdue(query, asDate = moment().format('YYYY-MM-DD')) {
|
||||
query.where('due_date', '<', asDate);
|
||||
},
|
||||
/**
|
||||
* Filters the not overdue invoices.
|
||||
*/
|
||||
notOverdue(query, asDate = moment().format('YYYY-MM-DD')) {
|
||||
query.where('due_date', '>=', asDate);
|
||||
},
|
||||
/**
|
||||
* Filters the partially invoices.
|
||||
*/
|
||||
partiallyPaid(query) {
|
||||
query.whereNot('payment_amount', 0);
|
||||
query.whereNot(raw('`PAYMENT_AMOUNT` = `BALANCE`'));
|
||||
},
|
||||
/**
|
||||
* Filters the paid invoices.
|
||||
*/
|
||||
paid(query) {
|
||||
query.where(raw('PAYMENT_AMOUNT = BALANCE'));
|
||||
},
|
||||
/**
|
||||
* Filters the sale invoices from the given date.
|
||||
*/
|
||||
fromDate(query, fromDate) {
|
||||
query.where('invoice_date', '<=', fromDate);
|
||||
},
|
||||
/**
|
||||
* Sort the sale invoices by full-payment invoices.
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(`PAYMENT_AMOUNT = BALANCE ${order}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort the sale invoices by the due amount.
|
||||
*/
|
||||
sortByDueAmount(query, order) {
|
||||
query.orderByRaw(`BALANCE - PAYMENT_AMOUNT ${order}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the max invoice
|
||||
*/
|
||||
maxInvoiceNo(query, prefix, number) {
|
||||
query
|
||||
.select(raw(`REPLACE(INVOICE_NO, "${prefix}", "") AS INV_NUMBER`))
|
||||
.havingRaw('CHAR_LENGTH(INV_NUMBER) = ??', [number.length])
|
||||
.orderBy('invNumber', 'DESC')
|
||||
.limit(1)
|
||||
.first();
|
||||
},
|
||||
|
||||
byPrefixAndNumber(query, prefix, number) {
|
||||
query.where('invoice_no', `${prefix}${number}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Status filter.
|
||||
*/
|
||||
statusFilter(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'delivered':
|
||||
query.modify('delivered');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
default:
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters by branches.
|
||||
*/
|
||||
filterByBranches(query, branchesIds) {
|
||||
const formattedBranchesIds = castArray(branchesIds);
|
||||
|
||||
query.whereIn('branchId', formattedBranchesIds);
|
||||
},
|
||||
|
||||
dueInvoicesFromDate(query, asDate = moment().format('YYYY-MM-DD')) {
|
||||
query.modify('dueInvoices');
|
||||
query.modify('notOverdue', asDate);
|
||||
query.modify('fromDate', asDate);
|
||||
},
|
||||
|
||||
overdueInvoicesFromDate(query, asDate = moment().format('YYYY-MM-DD')) {
|
||||
query.modify('dueInvoices');
|
||||
query.modify('overdue', asDate);
|
||||
query.modify('fromDate', asDate);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const Customer = require('models/Customer');
|
||||
const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
|
||||
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
||||
const Branch = require('models/Branch');
|
||||
const Warehouse = require('models/Warehouse');
|
||||
const Account = require('models/Account');
|
||||
const TaxRateTransaction = require('models/TaxRateTransaction');
|
||||
const Document = require('models/Document');
|
||||
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
||||
const {
|
||||
TransactionPaymentServiceEntry,
|
||||
} = require('models/TransactionPaymentServiceEntry');
|
||||
const { PdfTemplate } = require('models/PdfTemplate');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Sale invoice associated entries.
|
||||
*/
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'sales_invoices.id',
|
||||
to: 'items_entries.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'SaleInvoice');
|
||||
builder.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Belongs to customer model.
|
||||
*/
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Customer.default,
|
||||
join: {
|
||||
from: 'sales_invoices.customerId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'Customer');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoice has associated account transactions.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'sales_invoices.id',
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'SaleInvoice');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoice may has associated cost transactions.
|
||||
*/
|
||||
costTransactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: InventoryCostLotTracker.default,
|
||||
join: {
|
||||
from: 'sales_invoices.id',
|
||||
to: 'inventory_cost_lot_tracker.transactionId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('transaction_type', 'SaleInvoice');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoice may has associated payment entries.
|
||||
*/
|
||||
paymentEntries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: PaymentReceiveEntry.default,
|
||||
join: {
|
||||
from: 'sales_invoices.id',
|
||||
to: 'payment_receives_entries.invoiceId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoice may has associated branch.
|
||||
*/
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch.default,
|
||||
join: {
|
||||
from: 'sales_invoices.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoice may has associated warehouse.
|
||||
*/
|
||||
warehouse: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Warehouse.default,
|
||||
join: {
|
||||
from: 'sales_invoices.warehouseId',
|
||||
to: 'warehouses.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoice may has associated written-off expense account.
|
||||
*/
|
||||
writtenoffExpenseAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'sales_invoices.writtenoffExpenseAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoice may has associated tax rate transactions.
|
||||
*/
|
||||
taxes: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: TaxRateTransaction.default,
|
||||
join: {
|
||||
from: 'sales_invoices.id',
|
||||
to: 'tax_rate_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'SaleInvoice');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale invoice transaction may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'sales_invoices.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'SaleInvoice');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale invocie may belongs to matched bank transaction.
|
||||
*/
|
||||
matchedBankTransaction: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: MatchedBankTransaction,
|
||||
join: {
|
||||
from: 'sales_invoices.id',
|
||||
to: 'matched_bank_transactions.referenceId',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('reference_type', 'SaleInvoice');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale invoice may belongs to payment methods entries.
|
||||
*/
|
||||
paymentMethods: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: TransactionPaymentServiceEntry,
|
||||
join: {
|
||||
from: 'sales_invoices.id',
|
||||
to: 'transactions_payment_methods.referenceId',
|
||||
},
|
||||
beforeInsert: (model) => {
|
||||
model.referenceType = 'SaleInvoice';
|
||||
},
|
||||
filter: (query) => {
|
||||
query.where('reference_type', 'SaleInvoice');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale invoice may belongs to pdf branding template.
|
||||
*/
|
||||
pdfTemplate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: PdfTemplate,
|
||||
join: {
|
||||
from: 'sales_invoices.pdfTemplateId',
|
||||
to: 'pdf_templates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Change payment amount.
|
||||
* @param {Integer} invoiceId
|
||||
* @param {Numeric} amount
|
||||
*/
|
||||
static async changePaymentAmount(invoiceId, amount, trx) {
|
||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||
|
||||
await this.query(trx)
|
||||
.where('id', invoiceId)
|
||||
[changeMethod]('payment_amount', Math.abs(amount));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sale invoice meta.
|
||||
*/
|
||||
static get meta() {
|
||||
return SaleInvoiceMeta;
|
||||
}
|
||||
|
||||
static dueAmountFieldSortQuery(query, role) {
|
||||
query.modify('sortByDueAmount', role.order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model searchable.
|
||||
*/
|
||||
static get searchable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'invoice_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class SaleInvoiceEntry extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'sales_invoices_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
|
||||
return {
|
||||
saleInvoice: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'sales_invoices_entries.sale_invoice_id',
|
||||
to: 'sales_invoices.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
import { Features } from '@/interfaces';
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'receipt_date',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'receiptNumber',
|
||||
|
||||
print: {
|
||||
pageTitle: 'Sale Receipts',
|
||||
},
|
||||
fields: {
|
||||
amount: {
|
||||
name: 'receipt.field.amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
deposit_account: {
|
||||
column: 'deposit_account_id',
|
||||
name: 'receipt.field.deposit_account',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'depositAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
customer: {
|
||||
name: 'receipt.field.customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
receipt_date: {
|
||||
name: 'receipt.field.receipt_date',
|
||||
column: 'receipt_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
receipt_number: {
|
||||
name: 'receipt.field.receipt_number',
|
||||
column: 'receipt_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'receipt.field.reference_no',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
receipt_message: {
|
||||
name: 'receipt.field.receipt_message',
|
||||
column: 'receipt_message',
|
||||
fieldType: 'text',
|
||||
},
|
||||
statement: {
|
||||
name: 'receipt.field.statement',
|
||||
column: 'statement',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'receipt.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
status: {
|
||||
name: 'receipt.field.status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', label: 'receipt.field.status.draft' },
|
||||
{ key: 'closed', label: 'receipt.field.status.closed' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
depositAccount: {
|
||||
name: 'receipt.field.deposit_account',
|
||||
type: 'text',
|
||||
accessor: 'depositAccount.name',
|
||||
},
|
||||
customer: {
|
||||
name: 'receipt.field.customer',
|
||||
type: 'text',
|
||||
accessor: 'customer.displayName',
|
||||
},
|
||||
receiptDate: {
|
||||
name: 'receipt.field.receipt_date',
|
||||
accessor: 'formattedReceiptDate',
|
||||
type: 'date',
|
||||
},
|
||||
receiptNumber: {
|
||||
name: 'receipt.field.receipt_number',
|
||||
type: 'text',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'receipt.field.reference_no',
|
||||
column: 'reference_no',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
},
|
||||
receiptMessage: {
|
||||
name: 'receipt.field.receipt_message',
|
||||
column: 'receipt_message',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
amount: {
|
||||
name: 'receipt.field.amount',
|
||||
accessor: 'formattedAmount',
|
||||
type: 'number',
|
||||
},
|
||||
statement: {
|
||||
name: 'receipt.field.statement',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
status: {
|
||||
name: 'receipt.field.status',
|
||||
type: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', label: 'receipt.field.status.draft' },
|
||||
{ key: 'closed', label: 'receipt.field.status.closed' },
|
||||
],
|
||||
exportable: true,
|
||||
printable: false,
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
accessor: 'entries',
|
||||
type: 'collection',
|
||||
collectionOf: 'object',
|
||||
columns: {
|
||||
itemName: {
|
||||
name: 'Item Name',
|
||||
accessor: 'item.name',
|
||||
},
|
||||
rate: {
|
||||
name: 'Item Rate',
|
||||
accessor: 'rateFormatted',
|
||||
},
|
||||
quantity: {
|
||||
name: 'Item Quantity',
|
||||
accessor: 'quantityFormatted',
|
||||
},
|
||||
description: {
|
||||
name: 'Item Description',
|
||||
printable: false,
|
||||
},
|
||||
amount: {
|
||||
name: 'Item Amount',
|
||||
accessor: 'totalFormatted',
|
||||
},
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
name: 'receipt.field.created_at',
|
||||
type: 'date',
|
||||
printable: false,
|
||||
},
|
||||
branch: {
|
||||
name: 'Branch',
|
||||
type: 'text',
|
||||
accessor: 'branch.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
warehouse: {
|
||||
name: 'Warehouse',
|
||||
type: 'text',
|
||||
accessor: 'warehouse.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
receiptDate: {
|
||||
name: 'Receipt Date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
customerId: {
|
||||
name: 'Customer',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Contact',
|
||||
relationImportMatch: 'displayName',
|
||||
required: true,
|
||||
},
|
||||
depositAccountId: {
|
||||
name: 'Deposit Account',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Account',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'Exchange Rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
receiptNumber: {
|
||||
name: 'Receipt Number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'Reference No.',
|
||||
fieldType: 'text',
|
||||
},
|
||||
closed: {
|
||||
name: 'Closed',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
fieldType: 'collection',
|
||||
collectionOf: 'object',
|
||||
collectionMinLength: 1,
|
||||
required: true,
|
||||
fields: {
|
||||
itemId: {
|
||||
name: 'invoice.field.item_name',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'invoice.field.rate',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
quantity: {
|
||||
name: 'invoice.field.quantity',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
name: 'invoice.field.description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
statement: {
|
||||
name: 'Statement',
|
||||
fieldType: 'text',
|
||||
},
|
||||
receiptMessage: {
|
||||
name: 'Receipt Message',
|
||||
fieldType: 'text',
|
||||
},
|
||||
branchId: {
|
||||
name: 'Branch',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Branch',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.BRANCHES],
|
||||
required: true,
|
||||
},
|
||||
warehouseId: {
|
||||
name: 'Warehouse',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Warehouse',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.WAREHOUSES],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import SaleReceiptSettings from './SaleReceipt.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Sales/Receipts/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
import { defaultTo } from 'lodash';
|
||||
|
||||
export default class SaleReceipt extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public exchangeRate: number;
|
||||
public closedAt: Date;
|
||||
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
|
||||
public adjustment: number;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'sales_receipts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'localAmount',
|
||||
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
|
||||
'total',
|
||||
'totalLocal',
|
||||
|
||||
'adjustment',
|
||||
'adjustmentLocal',
|
||||
|
||||
'discountAmount',
|
||||
'discountAmountLocal',
|
||||
'discountPercentage',
|
||||
|
||||
'paid',
|
||||
'paidLocal',
|
||||
|
||||
'isClosed',
|
||||
'isDraft',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt subtotal in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.localAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountAmountLocal() {
|
||||
return this.discountAmount ? this.discountAmount * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage ? this.discount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt total.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
const adjustmentAmount = defaultTo(this.adjustment, 0);
|
||||
|
||||
return this.subtotal - this.discountAmount + adjustmentAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt total in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjustment amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get adjustmentLocal() {
|
||||
return this.adjustment * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt paid amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get paid() {
|
||||
return this.total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receipt paid amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get paidLocal() {
|
||||
return this.paid * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmine whether the sale receipt closed.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isClosed() {
|
||||
return !!this.closedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the sale receipt drafted.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isDraft() {
|
||||
return !this.closedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters the closed receipts.
|
||||
*/
|
||||
closed(query) {
|
||||
query.whereNot('closed_at', null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the invoices in draft status.
|
||||
*/
|
||||
draft(query) {
|
||||
query.where('closed_at', null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sorting the receipts order by status.
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(`CLOSED_AT IS NULL ${order}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filtering the receipts orders by status.
|
||||
*/
|
||||
filterByStatus(query, status) {
|
||||
switch (status) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'closed':
|
||||
default:
|
||||
query.modify('closed');
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Customer = require('models/Customer');
|
||||
const Account = require('models/Account');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const Branch = require('models/Branch');
|
||||
const Document = require('models/Document');
|
||||
const Warehouse = require('models/Warehouse');
|
||||
|
||||
return {
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Customer.default,
|
||||
join: {
|
||||
from: 'sales_receipts.customerId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'customer');
|
||||
},
|
||||
},
|
||||
|
||||
depositAccount: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'sales_receipts.depositAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'sales_receipts.id',
|
||||
to: 'items_entries.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'SaleReceipt');
|
||||
builder.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'sales_receipts.id',
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'SaleReceipt');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale receipt may belongs to branch.
|
||||
*/
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch.default,
|
||||
join: {
|
||||
from: 'sales_receipts.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale receipt may has associated warehouse.
|
||||
*/
|
||||
warehouse: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Warehouse.default,
|
||||
join: {
|
||||
from: 'sales_receipts.warehouseId',
|
||||
to: 'warehouses.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Sale receipt transaction may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'sales_receipts.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'SaleReceipt');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sale invoice meta.
|
||||
*/
|
||||
static get meta() {
|
||||
return SaleReceiptSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'receipt_number', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class SaleReceiptEntry extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'sales_receipt_entries';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const SaleReceipt = require('models/SaleReceipt');
|
||||
|
||||
return {
|
||||
saleReceipt: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleReceipt.default,
|
||||
join: {
|
||||
from: 'sales_receipt_entries.sale_receipt_id',
|
||||
to: 'sales_receipts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import Auth from './Auth';
|
||||
|
||||
export default class Setting extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra metadata query to query with the current authenticate user.
|
||||
* @param {Object} query
|
||||
*/
|
||||
static extraMetadataQuery(query) {
|
||||
if (Auth.isLogged()) {
|
||||
query.where('user_id', Auth.userId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
import { mixin, Model, raw } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import { ProjectTaskChargeType } from '@/services/Projects/Tasks/constants';
|
||||
import { number } from 'mathjs';
|
||||
|
||||
export default class Task extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
type!: string;
|
||||
rate!: number;
|
||||
actualHours!: number;
|
||||
invoicedHours!: number;
|
||||
estimateHours!: number;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'tasks';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'actualAmount',
|
||||
'invoicedAmount',
|
||||
'estimateAmount',
|
||||
'billableAmount',
|
||||
'billableHours',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the actual amount.
|
||||
*/
|
||||
get actualAmount(): number {
|
||||
return this.rate * this.actualHours;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the invoiced amount.
|
||||
*/
|
||||
get invoicedAmount(): number {
|
||||
return this.rate * this.invoicedHours;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the estimate amount.
|
||||
*/
|
||||
get estimateAmount(): number {
|
||||
return this.rate * this.estimateHours;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the billable amount.
|
||||
*/
|
||||
get billableAmount() {
|
||||
return this.actualAmount - this.invoicedAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the billable hours.
|
||||
*/
|
||||
get billableHours() {
|
||||
return this.actualHours - this.invoicedHours;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Sumation of total actual hours.
|
||||
* @param builder
|
||||
*/
|
||||
sumTotalActualHours(builder) {
|
||||
builder.sum('actualHours as totalActualHours');
|
||||
},
|
||||
|
||||
/**
|
||||
* Sumation total estimate hours.
|
||||
* @param builder
|
||||
*/
|
||||
sumTotalEstimateHours(builder) {
|
||||
builder.sum('estimateHours as totalEstimateHours');
|
||||
},
|
||||
|
||||
/**
|
||||
* Sumation of total invoiced hours.
|
||||
* @param builder
|
||||
*/
|
||||
sumTotalInvoicedHours(builder) {
|
||||
builder.sum('invoicedHours as totalInvoicedHours');
|
||||
},
|
||||
|
||||
/**
|
||||
* Sumation of total actual amount.
|
||||
* @param builder
|
||||
*/
|
||||
sumTotalActualAmount(builder) {
|
||||
builder.groupBy('totalActualAmount');
|
||||
builder.select(raw('ACTUAL_HOURS * RATE').as('totalActualAmount'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Sumation of total invoiced amount.
|
||||
* @param builder
|
||||
*/
|
||||
sumTotalInvoicedAmount(builder) {
|
||||
this.groupBy('totalInvoicedAmount');
|
||||
builder.select(raw('INVOICED_HOURS * RATE').as('totalInvoicedAmount'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Sumation of total estimate amount.
|
||||
* @param builder
|
||||
*/
|
||||
sumTotalEstimateAmount(builder) {
|
||||
builder.groupBy('totalEstimateAmount');
|
||||
builder.select(raw('ESTIMATE_HOURS * RATE').as('totalEstimateAmount'));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Time = require('models/Time');
|
||||
const Project = require('models/Project');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Project may has many associated tasks.
|
||||
*/
|
||||
times: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Time.default,
|
||||
join: {
|
||||
from: 'tasks.id',
|
||||
to: 'times.taskId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Project may has many associated times.
|
||||
*/
|
||||
project: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Project.default,
|
||||
join: {
|
||||
from: 'tasks.projectId',
|
||||
to: 'projects.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
export default {
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
exportable: true,
|
||||
importable: true,
|
||||
print: {
|
||||
pageTitle: 'Tax Rates',
|
||||
},
|
||||
columns: {
|
||||
name: {
|
||||
name: 'Tax Rate Name',
|
||||
type: 'text',
|
||||
accessor: 'name',
|
||||
},
|
||||
code: {
|
||||
name: 'Code',
|
||||
type: 'text',
|
||||
accessor: 'code',
|
||||
},
|
||||
rate: {
|
||||
name: 'Rate',
|
||||
type: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'Description',
|
||||
type: 'text',
|
||||
},
|
||||
isNonRecoverable: {
|
||||
name: 'Is Non Recoverable',
|
||||
type: 'boolean',
|
||||
},
|
||||
active: {
|
||||
name: 'Active',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
field: {},
|
||||
fields2: {
|
||||
name: {
|
||||
name: 'Tax name',
|
||||
fieldType: 'name',
|
||||
required: true,
|
||||
},
|
||||
code: {
|
||||
name: 'Code',
|
||||
fieldType: 'code',
|
||||
required: true,
|
||||
},
|
||||
rate: {
|
||||
name: 'Rate',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
name: 'Description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
isNonRecoverable: {
|
||||
name: 'Is Non Recoverable',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
active: {
|
||||
name: 'Active',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
import { mixin, Model, raw } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import SoftDeleteQueryBuilder from '@/collection/SoftDeleteQueryBuilder';
|
||||
import TaxRateMeta from './TaxRate.settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
|
||||
export default class TaxRate extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'tax_rates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft delete query builder.
|
||||
*/
|
||||
static get QueryBuilder() {
|
||||
return SoftDeleteQueryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the tax rate meta.
|
||||
*/
|
||||
static get meta() {
|
||||
return TaxRateMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { mixin, Model, raw } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class TaxRateTransaction extends mixin(TenantModel, [
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public rate: number;
|
||||
public referenceId: number;
|
||||
public referenceType: string;
|
||||
public taxRateId: number;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'tax_rate_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const TaxRate = require('models/TaxRate');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Belongs to the tax rate.
|
||||
*/
|
||||
taxRate: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TaxRate.default,
|
||||
join: {
|
||||
from: 'tax_rate_transactions.taxRateId',
|
||||
to: 'tax_rates.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Container } from 'typedi';
|
||||
import BaseModel from 'models/Model';
|
||||
|
||||
export default class TenantModel extends BaseModel {
|
||||
/**
|
||||
* Logging all tenant databases queries.
|
||||
* @param {...any} args
|
||||
*/
|
||||
static query(...args) {
|
||||
const Logger = Container.get('logger');
|
||||
|
||||
return super.query(...args).onBuildKnex((knexQueryBuilder) => {
|
||||
const { userParams: { tenantId } } = knexQueryBuilder.client.config;
|
||||
|
||||
knexQueryBuilder.on('query', queryData => {
|
||||
Logger.info(`[query][tenant] ${queryData.sql}`, {
|
||||
bindings: queryData.bindings, tenantId
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { mixin, Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class Time extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'times';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Task = require('models/Task');
|
||||
const Project = require('models/Project');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Project may has many associated tasks.
|
||||
*/
|
||||
task: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Task.default,
|
||||
join: {
|
||||
from: 'times.taskId',
|
||||
to: 'tasks.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Project may has many associated times.
|
||||
*/
|
||||
project: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Project.default,
|
||||
join: {
|
||||
from: 'times.projectId',
|
||||
to: 'projects.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export class TransactionPaymentServiceEntry extends TenantModel {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'transactions_payment_methods';
|
||||
}
|
||||
|
||||
/**
|
||||
* Json schema of the model.
|
||||
*/
|
||||
static get jsonSchema() {
|
||||
return {
|
||||
type: 'object',
|
||||
required: ['paymentIntegrationId'],
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
referenceId: { type: 'integer' },
|
||||
referenceType: { type: 'string' },
|
||||
paymentIntegrationId: { type: 'integer' },
|
||||
enable: { type: 'boolean' },
|
||||
options: { type: 'object' },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const { PaymentIntegration } = require('./PaymentIntegration');
|
||||
|
||||
return {
|
||||
paymentIntegration: {
|
||||
relation: TenantModel.BelongsToOneRelation,
|
||||
modelClass: PaymentIntegration,
|
||||
join: {
|
||||
from: 'transactions_payment_methods.paymentIntegrationId',
|
||||
to: 'payment_integrations.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
export default {
|
||||
defaultFilterField: 'createdAt',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
importable: true,
|
||||
fields: {
|
||||
date: {
|
||||
name: 'Date',
|
||||
column: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
payee: {
|
||||
name: 'Payee',
|
||||
column: 'payee',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
amount: {
|
||||
name: 'Amount',
|
||||
column: 'Amount',
|
||||
fieldType: 'numeric',
|
||||
required: true,
|
||||
},
|
||||
account: {
|
||||
name: 'Account',
|
||||
column: 'account_id',
|
||||
fieldType: 'relation',
|
||||
to: { model: 'Account', to: 'id' },
|
||||
},
|
||||
createdAt: {
|
||||
name: 'Created At',
|
||||
column: 'createdAt',
|
||||
fieldType: 'date',
|
||||
importable: false,
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
date: {
|
||||
name: 'Date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
payee: {
|
||||
name: 'Payee',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'Description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'Reference No.',
|
||||
fieldType: 'text',
|
||||
},
|
||||
amount: {
|
||||
name: 'Amount',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,248 +0,0 @@
|
||||
/* eslint-disable global-require */
|
||||
import moment from 'moment';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSettings from './ModelSetting';
|
||||
import UncategorizedCashflowTransactionMeta from './UncategorizedCashflowTransaction.meta';
|
||||
|
||||
export default class UncategorizedCashflowTransaction extends mixin(
|
||||
TenantModel,
|
||||
[ModelSettings]
|
||||
) {
|
||||
id!: number;
|
||||
date!: Date | string;
|
||||
|
||||
/**
|
||||
* Transaction amount.
|
||||
* Negative represents to spending and positive to deposit/card charge.
|
||||
* @param {number}
|
||||
*/
|
||||
amount!: number;
|
||||
categorized!: boolean;
|
||||
accountId!: number;
|
||||
referenceNo!: string;
|
||||
payee!: string;
|
||||
description!: string;
|
||||
plaidTransactionId!: string;
|
||||
recognizedTransactionId!: number;
|
||||
excludedAt: Date;
|
||||
pending: boolean;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'uncategorized_cashflow_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'withdrawal',
|
||||
'deposit',
|
||||
'isDepositTransaction',
|
||||
'isWithdrawalTransaction',
|
||||
'isRecognized',
|
||||
'isExcluded',
|
||||
'isPending',
|
||||
];
|
||||
}
|
||||
|
||||
static get meta() {
|
||||
return UncategorizedCashflowTransactionMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the withdrawal amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
public get withdrawal() {
|
||||
return this.amount < 0 ? Math.abs(this.amount) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the deposit amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
public get deposit(): number {
|
||||
return this.amount > 0 ? Math.abs(this.amount) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is deposit transaction.
|
||||
*/
|
||||
public get isDepositTransaction(): boolean {
|
||||
return 0 < this.deposit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is withdrawal transaction.
|
||||
*/
|
||||
public get isWithdrawalTransaction(): boolean {
|
||||
return 0 < this.withdrawal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is recognized.
|
||||
*/
|
||||
public get isRecognized(): boolean {
|
||||
return !!this.recognizedTransactionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is excluded.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public get isExcluded(): boolean {
|
||||
return !!this.excludedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the transaction is pending.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public get isPending(): boolean {
|
||||
return !!this.pending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters the not excluded transactions.
|
||||
*/
|
||||
notExcluded(query) {
|
||||
query.whereNull('excluded_at');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the excluded transactions.
|
||||
*/
|
||||
excluded(query) {
|
||||
query.whereNotNull('excluded_at');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter out the recognized transactions.
|
||||
* @param query
|
||||
*/
|
||||
recognized(query) {
|
||||
query.whereNotNull('recognizedTransactionId');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter out the not recognized transactions.
|
||||
* @param query
|
||||
*/
|
||||
notRecognized(query) {
|
||||
query.whereNull('recognizedTransactionId');
|
||||
},
|
||||
|
||||
categorized(query) {
|
||||
query.whereNotNull('categorizeRefType');
|
||||
query.whereNotNull('categorizeRefId');
|
||||
},
|
||||
|
||||
notCategorized(query) {
|
||||
query.whereNull('categorizeRefType');
|
||||
query.whereNull('categorizeRefId');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the not pending transactions.
|
||||
*/
|
||||
notPending(query) {
|
||||
query.where('pending', false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the pending transactions.
|
||||
*/
|
||||
pending(query) {
|
||||
query.where('pending', true);
|
||||
},
|
||||
|
||||
minAmount(query, minAmount) {
|
||||
query.where('amount', '>=', minAmount);
|
||||
},
|
||||
|
||||
maxAmount(query, maxAmount) {
|
||||
query.where('amount', '<=', maxAmount);
|
||||
},
|
||||
|
||||
toDate(query, toDate) {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const _toDate = moment(toDate).endOf('day').format(dateFormat);
|
||||
|
||||
query.where('date', '<=', _toDate);
|
||||
},
|
||||
|
||||
fromDate(query, fromDate) {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const _fromDate = moment(fromDate).startOf('day').format(dateFormat);
|
||||
|
||||
query.where('date', '>=', _fromDate);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Account = require('models/Account');
|
||||
const {
|
||||
RecognizedBankTransaction,
|
||||
} = require('models/RecognizedBankTransaction');
|
||||
const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Transaction may has associated to account.
|
||||
*/
|
||||
account: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: 'uncategorized_cashflow_transactions.accountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Transaction may has association to recognized transaction.
|
||||
*/
|
||||
recognizedTransaction: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: RecognizedBankTransaction,
|
||||
join: {
|
||||
from: 'uncategorized_cashflow_transactions.recognizedTransactionId',
|
||||
to: 'recognized_bank_transactions.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Uncategorized transaction may has association to matched transaction.
|
||||
*/
|
||||
matchedBankTransactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: MatchedBankTransaction,
|
||||
join: {
|
||||
from: 'uncategorized_cashflow_transactions.id',
|
||||
to: 'matched_bank_transactions.uncategorizedTransactionId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class User extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'users';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['isInviteAccepted', 'fullName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the user ivnite is accept.
|
||||
*/
|
||||
get isInviteAccepted() {
|
||||
return !!this.inviteAcceptedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full name attribute.
|
||||
*/
|
||||
get fullName() {
|
||||
return `${this.firstName} ${this.lastName}`.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Role = require('models/Role');
|
||||
|
||||
return {
|
||||
/**
|
||||
* User belongs to user.
|
||||
*/
|
||||
role: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Role.default,
|
||||
join: {
|
||||
from: 'users.roleId',
|
||||
to: 'roles.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,407 +0,0 @@
|
||||
export default {
|
||||
defaultFilterField: 'displayName',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
importable: true,
|
||||
exportable: true,
|
||||
fields: {
|
||||
first_name: {
|
||||
name: 'vendor.field.first_name',
|
||||
column: 'first_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
last_name: {
|
||||
name: 'vendor.field.last_name',
|
||||
column: 'last_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
display_name: {
|
||||
name: 'vendor.field.display_name',
|
||||
column: 'display_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
email: {
|
||||
name: 'vendor.field.email',
|
||||
column: 'email',
|
||||
fieldType: 'text',
|
||||
},
|
||||
work_phone: {
|
||||
name: 'vendor.field.work_phone',
|
||||
column: 'work_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
personal_phone: {
|
||||
name: 'vendor.field.personal_phone',
|
||||
column: 'personal_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
company_name: {
|
||||
name: 'vendor.field.company_name',
|
||||
column: 'company_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
website: {
|
||||
name: 'vendor.field.website',
|
||||
column: 'website',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'vendor.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
balance: {
|
||||
name: 'vendor.field.balance',
|
||||
column: 'balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
opening_balance: {
|
||||
name: 'vendor.field.opening_balance',
|
||||
column: 'opening_balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
opening_balance_at: {
|
||||
name: 'vendor.field.opening_balance_at',
|
||||
column: 'opening_balance_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
currency_code: {
|
||||
name: 'vendor.field.currency',
|
||||
column: 'currency_code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
name: 'vendor.field.status',
|
||||
type: 'enumeration',
|
||||
options: [
|
||||
{ key: 'overdue', label: 'vendor.field.status.overdue' },
|
||||
{ key: 'unpaid', label: 'vendor.field.status.unpaid' },
|
||||
],
|
||||
filterCustomQuery: (query, role) => {
|
||||
switch (role.value) {
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
firstName: {
|
||||
name: 'vendor.field.first_name',
|
||||
type: 'text',
|
||||
},
|
||||
lastName: {
|
||||
name: 'vendor.field.last_name',
|
||||
type: 'text',
|
||||
},
|
||||
displayName: {
|
||||
name: 'vendor.field.display_name',
|
||||
type: 'text',
|
||||
},
|
||||
email: {
|
||||
name: 'vendor.field.email',
|
||||
type: 'text',
|
||||
},
|
||||
workPhone: {
|
||||
name: 'vendor.field.work_phone',
|
||||
type: 'text',
|
||||
},
|
||||
personalPhone: {
|
||||
name: 'vendor.field.personal_phone',
|
||||
type: 'text',
|
||||
},
|
||||
companyName: {
|
||||
name: 'vendor.field.company_name',
|
||||
type: 'text',
|
||||
},
|
||||
website: {
|
||||
name: 'vendor.field.website',
|
||||
type: 'text',
|
||||
},
|
||||
balance: {
|
||||
name: 'vendor.field.balance',
|
||||
type: 'number',
|
||||
},
|
||||
openingBalance: {
|
||||
name: 'vendor.field.opening_balance',
|
||||
type: 'number',
|
||||
printable: false
|
||||
},
|
||||
openingBalanceAt: {
|
||||
name: 'vendor.field.opening_balance_at',
|
||||
type: 'date',
|
||||
printable: false
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'vendor.field.currency',
|
||||
type: 'text',
|
||||
printable: false
|
||||
},
|
||||
status: {
|
||||
name: 'vendor.field.status',
|
||||
printable: false
|
||||
},
|
||||
note: {
|
||||
name: 'vendor.field.note',
|
||||
type: 'text',
|
||||
printable: false
|
||||
},
|
||||
// Billing Address
|
||||
billingAddress1: {
|
||||
name: 'Billing Address 1',
|
||||
column: 'billing_address1',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
billingAddress2: {
|
||||
name: 'Billing Address 2',
|
||||
column: 'billing_address2',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
billingAddressCity: {
|
||||
name: 'Billing Address City',
|
||||
column: 'billing_address_city',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
billingAddressCountry: {
|
||||
name: 'Billing Address Country',
|
||||
column: 'billing_address_country',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
billingAddressPostcode: {
|
||||
name: 'Billing Address Postcode',
|
||||
column: 'billing_address_postcode',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
billingAddressState: {
|
||||
name: 'Billing Address State',
|
||||
column: 'billing_address_state',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
billingAddressPhone: {
|
||||
name: 'Billing Address Phone',
|
||||
column: 'billing_address_phone',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
// Shipping Address
|
||||
shippingAddress1: {
|
||||
name: 'Shipping Address 1',
|
||||
column: 'shipping_address1',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
shippingAddress2: {
|
||||
name: 'Shipping Address 2',
|
||||
column: 'shipping_address2',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
shippingAddressCity: {
|
||||
name: 'Shipping Address City',
|
||||
column: 'shipping_address_city',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
shippingAddressCountry: {
|
||||
name: 'Shipping Address Country',
|
||||
column: 'shipping_address_country',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
shippingAddressPostcode: {
|
||||
name: 'Shipping Address Postcode',
|
||||
column: 'shipping_address_postcode',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
shippingAddressState: {
|
||||
name: 'Shipping Address State',
|
||||
column: 'shipping_address_state',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
shippingAddressPhone: {
|
||||
name: 'Shipping Address Phone',
|
||||
column: 'shipping_address_phone',
|
||||
type: 'text',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
createdAt: {
|
||||
name: 'vendor.field.created_at',
|
||||
type: 'date',
|
||||
exportable: true,
|
||||
printable: false
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
firstName: {
|
||||
name: 'vendor.field.first_name',
|
||||
column: 'first_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
lastName: {
|
||||
name: 'vendor.field.last_name',
|
||||
column: 'last_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
displayName: {
|
||||
name: 'vendor.field.display_name',
|
||||
column: 'display_name',
|
||||
fieldType: 'text',
|
||||
required: true,
|
||||
},
|
||||
email: {
|
||||
name: 'vendor.field.email',
|
||||
column: 'email',
|
||||
fieldType: 'text',
|
||||
},
|
||||
workPhone: {
|
||||
name: 'vendor.field.work_phone',
|
||||
column: 'work_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
personalPhone: {
|
||||
name: 'vendor.field.personal_phone',
|
||||
column: 'personal_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
companyName: {
|
||||
name: 'vendor.field.company_name',
|
||||
column: 'company_name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
website: {
|
||||
name: 'vendor.field.website',
|
||||
column: 'website',
|
||||
fieldType: 'text',
|
||||
},
|
||||
openingBalance: {
|
||||
name: 'vendor.field.opening_balance',
|
||||
column: 'opening_balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
openingBalanceAt: {
|
||||
name: 'vendor.field.opening_balance_at',
|
||||
column: 'opening_balance_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
openingBalanceExchangeRate: {
|
||||
name: 'Opening Balance Ex. Rate',
|
||||
column: 'opening_balance_exchange_rate',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currencyCode: {
|
||||
name: 'vendor.field.currency',
|
||||
column: 'currency_code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
active: {
|
||||
name: 'Active',
|
||||
column: 'active',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
// Billing Address
|
||||
billingAddress1: {
|
||||
name: 'Billing Address 1',
|
||||
column: 'billing_address1',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddress2: {
|
||||
name: 'Billing Address 2',
|
||||
column: 'billing_address2',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddressCity: {
|
||||
name: 'Billing Address City',
|
||||
column: 'billing_address_city',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddressCountry: {
|
||||
name: 'Billing Address Country',
|
||||
column: 'billing_address_country',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddressPostcode: {
|
||||
name: 'Billing Address Postcode',
|
||||
column: 'billing_address_postcode',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddressState: {
|
||||
name: 'Billing Address State',
|
||||
column: 'billing_address_state',
|
||||
fieldType: 'text',
|
||||
},
|
||||
billingAddressPhone: {
|
||||
name: 'Billing Address Phone',
|
||||
column: 'billing_address_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
// Shipping Address
|
||||
shippingAddress1: {
|
||||
name: 'Shipping Address 1',
|
||||
column: 'shipping_address1',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddress2: {
|
||||
name: 'Shipping Address 2',
|
||||
column: 'shipping_address2',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddressCity: {
|
||||
name: 'Shipping Address City',
|
||||
column: 'shipping_address_city',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddressCountry: {
|
||||
name: 'Shipping Address Country',
|
||||
column: 'shipping_address_country',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddressPostcode: {
|
||||
name: 'Shipping Address Postcode',
|
||||
column: 'shipping_address_postcode',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddressState: {
|
||||
name: 'Shipping Address State',
|
||||
column: 'shipping_address_state',
|
||||
fieldType: 'text',
|
||||
},
|
||||
shippingAddressPhone: {
|
||||
name: 'Shipping Address Phone',
|
||||
column: 'shipping_address_phone',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,179 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import PaginationQueryBuilder from './Pagination';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import VendorSettings from './Vendor.Settings';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Contacts/Vendors/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
class VendorQueryBuilder extends PaginationQueryBuilder {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.onBuild((builder) => {
|
||||
if (builder.isFind() || builder.isDelete() || builder.isUpdate()) {
|
||||
builder.where('contact_service', 'vendor');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default class Vendor extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Query builder.
|
||||
*/
|
||||
static get QueryBuilder() {
|
||||
return VendorQueryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'contacts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defined virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['closingBalance', 'contactNormal', 'localOpeningBalance'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Closing balance attribute.
|
||||
*/
|
||||
get closingBalance() {
|
||||
return this.balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the local opening balance.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localOpeningBalance() {
|
||||
return this.openingBalance
|
||||
? this.openingBalance * this.openingBalanceExchangeRate
|
||||
: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the contact noraml;
|
||||
*/
|
||||
get contactNormal() {
|
||||
return 'debit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('active', !active);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the active customers.
|
||||
*/
|
||||
active(query) {
|
||||
query.where('active', 1);
|
||||
},
|
||||
/**
|
||||
* Filters the inactive customers.
|
||||
*/
|
||||
inactive(query) {
|
||||
query.where('active', 0);
|
||||
},
|
||||
/**
|
||||
* Filters the vendors that have overdue invoices.
|
||||
*/
|
||||
overdue(query) {
|
||||
query.select(
|
||||
'*',
|
||||
Vendor.relatedQuery('overdueBills', query.knex())
|
||||
.count()
|
||||
.as('countOverdue')
|
||||
);
|
||||
query.having('countOverdue', '>', 0);
|
||||
},
|
||||
/**
|
||||
* Filters the unpaid customers.
|
||||
*/
|
||||
unpaid(query) {
|
||||
query.whereRaw('`BALANCE` + `OPENING_BALANCE` <> 0');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Bill = require('models/Bill');
|
||||
|
||||
return {
|
||||
bills: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Bill.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'bills.vendorId',
|
||||
},
|
||||
},
|
||||
overdueBills: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Bill.default,
|
||||
join: {
|
||||
from: 'contacts.id',
|
||||
to: 'bills.vendorId',
|
||||
},
|
||||
filter: (query) => {
|
||||
query.modify('overdue');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get meta() {
|
||||
return VendorSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'display_name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'first_name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'last_name', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'company_name', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'email', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'work_phone', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'personal_phone', comparator: 'equals' },
|
||||
{ condition: 'or', fieldKey: 'website', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
import { Features } from '@/interfaces';
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
exportable: true,
|
||||
exportFlattenOn: 'entries',
|
||||
|
||||
importable: true,
|
||||
importAggregator: 'group',
|
||||
importAggregateOn: 'entries',
|
||||
importAggregateBy: 'vendorCreditNumber',
|
||||
|
||||
print: {
|
||||
pageTitle: 'Vendor Credits',
|
||||
},
|
||||
fields: {
|
||||
vendor: {
|
||||
name: 'vendor_credit.field.vendor',
|
||||
column: 'vendor_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'vendor',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
amount: {
|
||||
name: 'vendor_credit.field.amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currency_code: {
|
||||
name: 'vendor_credit.field.currency_code',
|
||||
column: 'currency_code',
|
||||
fieldType: 'string',
|
||||
},
|
||||
credit_date: {
|
||||
name: 'vendor_credit.field.credit_date',
|
||||
column: 'vendor_credit_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'vendor_credit.field.reference_no',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
credit_number: {
|
||||
name: 'vendor_credit.field.credit_number',
|
||||
column: 'vendor_credit_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
note: {
|
||||
name: 'vendor_credit.field.note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
status: {
|
||||
name: 'vendor_credit.field.status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', label: 'vendor_credit.field.status.draft' },
|
||||
{ key: 'published', label: 'vendor_credit.field.status.published' },
|
||||
{ key: 'open', label: 'vendor_credit.field.status.open' },
|
||||
{ key: 'closed', label: 'vendor_credit.field.status.closed' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
created_at: {
|
||||
name: 'vendor_credit.field.created_at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
vendorId: {
|
||||
name: 'Vendor',
|
||||
type: 'relation',
|
||||
accessor: 'vendor.displayName',
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'Echange Rate',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
vendorCreditNumber: {
|
||||
name: 'Vendor Credit No.',
|
||||
type: 'text',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'Refernece No.',
|
||||
type: 'text',
|
||||
},
|
||||
vendorCreditDate: {
|
||||
name: 'Vendor Credit Date',
|
||||
accessor: 'formattedVendorCreditDate',
|
||||
},
|
||||
amount: {
|
||||
name: 'Amount',
|
||||
accessor: 'formattedAmount',
|
||||
},
|
||||
creditRemaining: {
|
||||
name: 'Credits Remaining',
|
||||
accessor: 'formattedCreditsRemaining',
|
||||
printable: false,
|
||||
},
|
||||
refundedAmount: {
|
||||
name: 'Refunded Amount',
|
||||
accessor: 'refundedAmount',
|
||||
printable: false,
|
||||
},
|
||||
invoicedAmount: {
|
||||
name: 'Invoiced Amount',
|
||||
accessor: 'formattedInvoicedAmount',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
type: 'text',
|
||||
printable: false,
|
||||
},
|
||||
open: {
|
||||
name: 'Open',
|
||||
type: 'boolean',
|
||||
printable: false,
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
type: 'collection',
|
||||
collectionOf: 'object',
|
||||
columns: {
|
||||
itemName: {
|
||||
name: 'Item Name',
|
||||
accessor: 'item.name',
|
||||
},
|
||||
rate: {
|
||||
name: 'Item Rate',
|
||||
accessor: 'rateFormatted',
|
||||
},
|
||||
quantity: {
|
||||
name: 'Item Quantity',
|
||||
accessor: 'quantityFormatted',
|
||||
},
|
||||
description: {
|
||||
name: 'Item Description',
|
||||
},
|
||||
amount: {
|
||||
name: 'Item Amount',
|
||||
accessor: 'totalFormatted',
|
||||
},
|
||||
},
|
||||
},
|
||||
branch: {
|
||||
name: 'Branch',
|
||||
type: 'text',
|
||||
accessor: 'branch.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
warehouse: {
|
||||
name: 'Warehouse',
|
||||
type: 'text',
|
||||
accessor: 'warehouse.name',
|
||||
features: [Features.BRANCHES],
|
||||
},
|
||||
},
|
||||
fields2: {
|
||||
vendorId: {
|
||||
name: 'Vendor',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Contact',
|
||||
relationImportMatch: 'displayName',
|
||||
required: true,
|
||||
},
|
||||
exchangeRate: {
|
||||
name: 'Echange Rate',
|
||||
fieldType: 'text',
|
||||
},
|
||||
vendorCreditNumber: {
|
||||
name: 'Vendor Credit No.',
|
||||
fieldType: 'text',
|
||||
},
|
||||
referenceNo: {
|
||||
name: 'Refernece No.',
|
||||
fieldType: 'text',
|
||||
},
|
||||
vendorCreditDate: {
|
||||
name: 'Vendor Credit Date',
|
||||
fieldType: 'date',
|
||||
required: true,
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
open: {
|
||||
name: 'Open',
|
||||
fieldType: 'boolean',
|
||||
},
|
||||
entries: {
|
||||
name: 'Entries',
|
||||
fieldType: 'collection',
|
||||
collectionOf: 'object',
|
||||
collectionMinLength: 1,
|
||||
required: true,
|
||||
fields: {
|
||||
itemId: {
|
||||
name: 'Item Name',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Item',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
required: true,
|
||||
importHint: 'Matches the item name or code.',
|
||||
},
|
||||
rate: {
|
||||
name: 'Rate',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
quantity: {
|
||||
name: 'Quantity',
|
||||
fieldType: 'number',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
name: 'Description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
branchId: {
|
||||
name: 'Branch',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Branch',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.BRANCHES],
|
||||
required: true
|
||||
},
|
||||
warehouseId: {
|
||||
name: 'Warehouse',
|
||||
fieldType: 'relation',
|
||||
relationModel: 'Warehouse',
|
||||
relationImportMatch: ['name', 'code'],
|
||||
features: [Features.WAREHOUSES],
|
||||
required: true
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,369 +0,0 @@
|
||||
import { Model, raw, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import BillSettings from './Bill.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import { DEFAULT_VIEWS } from '@/services/Purchases/VendorCredits/constants';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
import VendorCreditMeta from './VendorCredit.Meta';
|
||||
import { DiscountType } from '@/interfaces';
|
||||
|
||||
export default class VendorCredit extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public exchangeRate: number;
|
||||
public openedAt: Date;
|
||||
public discount: number;
|
||||
public discountType: DiscountType;
|
||||
public adjustment: number;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'vendor_credits';
|
||||
}
|
||||
/**
|
||||
* Vendor credit amount in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor credit subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor credit subtotal in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.subtotal * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
get discountAmount() {
|
||||
return this.discountType === DiscountType.Amount
|
||||
? this.discount
|
||||
: this.subtotal * (this.discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountAmountLocal() {
|
||||
return this.discountAmount ? this.discountAmount * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discount percentage.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get discountPercentage(): number | null {
|
||||
return this.discountType === DiscountType.Percentage ? this.discount : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjustment amount in local currency.
|
||||
* @returns {number | null}
|
||||
*/
|
||||
get adjustmentLocal() {
|
||||
return this.adjustment ? this.adjustment * this.exchangeRate : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor credit total.
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.subtotal - this.discountAmount + this.adjustment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vendor credit total in local currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Filters the credit notes in draft status.
|
||||
*/
|
||||
draft(query) {
|
||||
query.where('opened_at', null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the published vendor credits.
|
||||
*/
|
||||
published(query) {
|
||||
query.whereNot('opened_at', null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the open vendor credits.
|
||||
*/
|
||||
open(query) {
|
||||
query
|
||||
.where(
|
||||
raw(`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICED_AMOUNT) <
|
||||
COALESCE(AMOUNT)`)
|
||||
)
|
||||
.modify('published');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the closed vendor credits.
|
||||
*/
|
||||
closed(query) {
|
||||
query
|
||||
.where(
|
||||
raw(`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICED_AMOUNT) =
|
||||
COALESCE(AMOUNT)`)
|
||||
)
|
||||
.modify('published');
|
||||
},
|
||||
|
||||
/**
|
||||
* Status filter.
|
||||
*/
|
||||
filterByStatus(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'published':
|
||||
query.modify('published');
|
||||
break;
|
||||
case 'open':
|
||||
default:
|
||||
query.modify('open');
|
||||
break;
|
||||
case 'closed':
|
||||
query.modify('closed');
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(
|
||||
`COALESCE(REFUNDED_AMOUNT) + COALESCE(INVOICED_AMOUNT) = COALESCE(AMOUNT) ${order}`
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'isDraft',
|
||||
'isPublished',
|
||||
'isOpen',
|
||||
'isClosed',
|
||||
|
||||
'creditsRemaining',
|
||||
'localAmount',
|
||||
|
||||
'discountAmount',
|
||||
'discountAmountLocal',
|
||||
'discountPercentage',
|
||||
|
||||
'adjustmentLocal',
|
||||
|
||||
'total',
|
||||
'totalLocal',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the vendor credit is draft.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isDraft() {
|
||||
return !this.openedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether vendor credit is published.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isPublished() {
|
||||
return !!this.openedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the credit note is open.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isOpen() {
|
||||
return !!this.openedAt && this.creditsRemaining > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the credit note is closed.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isClosed() {
|
||||
return this.openedAt && this.creditsRemaining === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the credits remaining.
|
||||
* @returns {number}
|
||||
*/
|
||||
get creditsRemaining() {
|
||||
return Math.max(this.amount - this.refundedAmount - this.invoicedAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bill model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return BillSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Vendor = require('models/Vendor');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const Branch = require('models/Branch');
|
||||
const Document = require('models/Document');
|
||||
const Warehouse = require('models/Warehouse');
|
||||
|
||||
return {
|
||||
vendor: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Vendor.default,
|
||||
join: {
|
||||
from: 'vendor_credits.vendorId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'vendor');
|
||||
},
|
||||
},
|
||||
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'vendor_credits.id',
|
||||
to: 'items_entries.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'VendorCredit');
|
||||
builder.orderBy('index', 'ASC');
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Vendor credit may belongs to branch.
|
||||
*/
|
||||
branch: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Branch.default,
|
||||
join: {
|
||||
from: 'vendor_credits.branchId',
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Vendor credit may has associated warehouse.
|
||||
*/
|
||||
warehouse: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Warehouse.default,
|
||||
join: {
|
||||
from: 'vendor_credits.warehouseId',
|
||||
to: 'warehouses.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Vendor credit may has many attached attachments.
|
||||
*/
|
||||
attachments: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Document.default,
|
||||
join: {
|
||||
from: 'vendor_credits.id',
|
||||
through: {
|
||||
from: 'document_links.modelId',
|
||||
to: 'document_links.documentId',
|
||||
},
|
||||
to: 'documents.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('model_ref', 'VendorCredit');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static get meta() {
|
||||
return VendorCreditMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return DEFAULT_VIEWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model search attributes.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ fieldKey: 'credit_number', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents mutate base currency since the model is not empty.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static get preventMutateBaseCurrency() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { mixin, Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomViewBaseModel from './CustomViewBaseModel';
|
||||
import ModelSearchable from './ModelSearchable';
|
||||
|
||||
export default class VendorCreditAppliedBill extends mixin(TenantModel, [
|
||||
ModelSetting,
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'vendor_credit_applied_bill';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Bill = require('models/Bill');
|
||||
const VendorCredit = require('models/VendorCredit');
|
||||
|
||||
return {
|
||||
bill: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Bill.default,
|
||||
join: {
|
||||
from: 'vendor_credit_applied_bill.billId',
|
||||
to: 'bills.id',
|
||||
},
|
||||
},
|
||||
|
||||
vendorCredit: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: VendorCredit.default,
|
||||
join: {
|
||||
from: 'vendor_credit_applied_bill.vendorCreditId',
|
||||
to: 'vendor_credits.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class View extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'views';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
static get modifiers() {
|
||||
const TABLE_NAME = View.tableName;
|
||||
|
||||
return {
|
||||
allMetadata(query) {
|
||||
query.withGraphFetched('roles.field');
|
||||
query.withGraphFetched('columns');
|
||||
},
|
||||
|
||||
specificOrFavourite(query, viewId) {
|
||||
if (viewId) {
|
||||
query.where('id', viewId)
|
||||
} else {
|
||||
query.where('favourite', true);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const ViewColumn = require('models/ViewColumn');
|
||||
const ViewRole = require('models/ViewRole');
|
||||
|
||||
return {
|
||||
/**
|
||||
* View model may has many columns.
|
||||
*/
|
||||
columns: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ViewColumn.default,
|
||||
join: {
|
||||
from: 'views.id',
|
||||
to: 'view_has_columns.viewId',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* View model may has many view roles.
|
||||
*/
|
||||
roles: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ViewRole.default,
|
||||
join: {
|
||||
from: 'views.id',
|
||||
to: 'view_roles.viewId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Model } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
|
||||
export default class ViewColumn extends TenantModel {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'view_has_columns';
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
|
||||
return {
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user