feat: rewrite repositories with base entity repository class.

feat: sales and purchases status.
feat: sales and purchases auto-increment number.
fix: settings find query with extra columns.
This commit is contained in:
Ahmed Bouhuolia
2020-12-13 19:50:59 +02:00
parent e9e4ddaee0
commit 188e411f02
78 changed files with 1634 additions and 869 deletions

View File

@@ -1,15 +1,9 @@
import { Model, raw } from 'objection';
import moment from 'moment';
import { difference } from 'lodash';
import TenantModel from 'models/TenantModel';
export default class Bill extends TenantModel {
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return ['dueAmount'];
}
/**
* Table name
*/
@@ -36,6 +30,13 @@ export default class Bill extends TenantModel {
return ['createdAt', 'updatedAt'];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return ['dueAmount', 'isOpen', 'isPartiallyPaid', 'isFullyPaid', 'isPaid', 'remainingDays', 'overdueDays', 'isOverdue'];
}
/**
* Due amount of the given.
* @return {number}
@@ -44,6 +45,74 @@ export default class Bill extends TenantModel {
return Math.max(this.amount - this.paymentAmount, 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.amount && 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() {
// Can't continue in case due date not defined.
if (!this.dueDate) { return null; }
const date = moment();
const dueDate = moment(this.dueDate);
return Math.max(dueDate.diff(date, 'days'), 0);
}
/**
* Retrieve the overdue days in number.
* @return {number|null}
*/
get overdueDays() {
// Can't continue in case due date not defined.
if (!this.dueDate) { return null; }
const date = moment();
const dueDate = moment(this.dueDate);
return Math.max(date.diff(dueDate, 'days'), 0);
}
/**
* Detarmines the due date is over.
* @return {boolean}
*/
get isOverdue() {
return this.overdueDays > 0;
}
/**
* Relationship mapping.
*/

View File

@@ -26,7 +26,7 @@ export default class Contact extends TenantModel {
/**
* Closing balance attribute.
*/
closingBalance() {
get closingBalance() {
return this.openingBalance + this.balance;
}
@@ -77,66 +77,6 @@ export default class Contact extends TenantModel {
};
}
/**
* Change vendor balance.
* @param {Integer} customerId
* @param {Numeric} amount
*/
static async changeBalance(customerId, amount) {
const changeMethod = (amount > 0) ? 'increment' : 'decrement';
return this.query()
.where('id', customerId)
[changeMethod]('balance', Math.abs(amount));
}
/**
* Increment the given customer balance.
* @param {Integer} customerId
* @param {Integer} amount
*/
static async incrementBalance(customerId, amount) {
return this.query()
.where('id', customerId)
.increment('balance', amount);
}
/**
* Decrement the given customer balance.
* @param {integer} customerId -
* @param {integer} amount -
*/
static async decrementBalance(customerId, amount) {
await this.query()
.where('id', customerId)
.decrement('balance', amount);
}
/**
*
* @param {number} customerId
* @param {number} oldCustomerId
* @param {number} amount
* @param {number} oldAmount
*/
static changeDiffBalance(customerId, oldCustomerId, amount, oldAmount) {
const diffAmount = amount - oldAmount;
const asyncOpers = [];
if (customerId != oldCustomerId) {
const oldCustomerOper = this.changeBalance(oldCustomerId, (oldAmount * -1));
const customerOper = this.changeBalance(customerId, amount);
asyncOpers.push(customerOper);
asyncOpers.push(oldCustomerOper);
} else {
const balanceChangeOper = this.changeBalance(customerId, diffAmount);
asyncOpers.push(balanceChangeOper);
}
return Promise.all(asyncOpers);
}
static get fields() {
return {
created_at: {

View File

@@ -0,0 +1,78 @@
import { Model, QueryBuilder } from 'objection';
import TenantModel from 'models/TenantModel';
class CustomerQueryBuilder extends QueryBuilder {
constructor(...args) {
super(...args);
this.onBuild((builder) => {
if (builder.isFind() || builder.isDelete() || builder.isUpdate()) {
builder.where('contact_service', 'customer');
}
});
}
}
export default class Customer extends TenantModel {
/**
* 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 ['closingBalance'];
}
/**
* Closing balance attribute.
*/
get closingBalance() {
return this.openingBalance + this.balance;
}
/**
* 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',
},
},
};
}
static get fields() {
return {
created_at: {
column: 'created_at',
}
};
}
}

View File

@@ -1,7 +1,6 @@
import { Model } from "objection";
import TenantModel from "models/TenantModel";
import { viewRolesBuilder } from "lib/ViewRolesBuilder";
import Media from "./Media";
export default class Expense extends TenantModel {
/**

View File

@@ -1,5 +1,7 @@
import { Model, mixin } from 'objection';
import moment from 'moment';
import { Model } from 'objection';
import TenantModel from 'models/TenantModel';
import { defaultToTransform } from 'utils';
export default class SaleEstimate extends TenantModel {
/**
@@ -16,6 +18,41 @@ export default class SaleEstimate extends TenantModel {
return ['createdAt', 'updatedAt'];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return ['isDelivered', 'isExpired', 'isConvertedToInvoice'];
}
/**
* 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,
);
}
/**
* Allows to mark model as resourceable to viewable and filterable.
*/

View File

@@ -1,6 +1,7 @@
import { Model, raw } from 'objection';
import moment from 'moment';
import TenantModel from 'models/TenantModel';
import { defaultToTransform } from 'utils';
export default class SaleInvoice extends TenantModel {
/**
@@ -24,6 +25,90 @@ export default class SaleInvoice extends TenantModel {
return ['created_at', 'updated_at'];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return ['dueAmount', 'isDelivered', 'isOverdue', 'isPartiallyPaid', 'isFullyPaid', 'isPaid', 'remainingDays', 'overdueDays'];
}
/**
* 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 invoice due amount.
* (Invoice amount - payment amount = Due amount)
* @return {boolean}
*/
dueAmount() {
return Math.max(this.balance - this.paymentAmount, 0);
}
/**
* Detarmine whether the invoice paid partially.
* @return {boolean}
*/
get isPartiallyPaid() {
return this.dueAmount !== this.balance && 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;
}
/**
* Retrieve the remaining days in number
* @return {number|null}
*/
get remainingDays() {
// Can't continue in case due date not defined.
if (!this.dueDate) { return null; }
const date = moment();
const dueDate = moment(this.dueDate);
return Math.max(dueDate.diff(date, 'days'), 0);
}
/**
* Retrieve the overdue days in number.
* @return {number|null}
*/
get overdueDays() {
// Can't continue in case due date not defined.
if (!this.dueDate) { return null; }
const date = moment();
const dueDate = moment(this.dueDate);
return Math.max(date.diff(dueDate, 'days'), 0);
}
static get resourceable() {
return true;
}
@@ -67,6 +152,7 @@ export default class SaleInvoice extends TenantModel {
const ItemEntry = require('models/ItemEntry');
const Contact = require('models/Contact');
const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
return {
entries: {
@@ -115,7 +201,16 @@ export default class SaleInvoice extends TenantModel {
filter(builder) {
builder.where('transaction_type', 'SaleInvoice');
},
}
},
paymentEntries: {
relation: Model.HasManyRelation,
modelClass: PaymentReceiveEntry.default,
join: {
from: 'sales_invoices.id',
to: 'payment_receives_entries.invoice_id',
},
},
};
}

View File

@@ -8,7 +8,8 @@ export default class TenantModel extends BaseModel {
*/
static query(...args) {
const Logger = Container.get('logger');
return super.query(...args).onBuildKnex(knexQueryBuilder => {
return super.query(...args).onBuildKnex((knexQueryBuilder) => {
const { userParams: { tenantId } } = knexQueryBuilder.client.config;
knexQueryBuilder.on('query', queryData => {

View File

@@ -0,0 +1,78 @@
import { Model, QueryBuilder } from 'objection';
import TenantModel from 'models/TenantModel';
class VendorQueryBuilder extends QueryBuilder {
constructor(...args) {
super(...args);
this.onBuild((builder) => {
if (builder.isFind() || builder.isDelete() || builder.isUpdate()) {
builder.where('contact_service', 'vendor');
}
});
}
}
export default class Vendor extends TenantModel {
/**
* 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'];
}
/**
* Closing balance attribute.
*/
get closingBalance() {
return this.openingBalance + this.balance;
}
/**
* 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',
},
}
};
}
static get fields() {
return {
created_at: {
column: 'created_at',
}
};
}
}

View File

@@ -18,6 +18,12 @@ import ItemEntry from './ItemEntry';
import InventoryTransaction from './InventoryTransaction';
import AccountType from './AccountType';
import InventoryLotCostTracker from './InventoryCostLotTracker';
import Customer from './Customer';
import Contact from './Contact';
import Vendor from './Vendor';
import ExpenseCategory from './ExpenseCategory';
import Expense from './Expense';
import ManualJournal from './ManualJournal';
export {
SaleEstimate,
@@ -40,4 +46,10 @@ export {
InventoryLotCostTracker,
AccountType,
Option,
Contact,
ExpenseCategory,
Expense,
ManualJournal,
Customer,
Vendor,
};