mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
feat: rewrite repositories with base entity repository class.
feat: sales and purchases status. feat: sales and purchases auto-increment number. fix: settings find query with extra columns.
This commit is contained in:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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: {
|
||||
|
||||
78
server/src/models/Customer.js
Normal file
78
server/src/models/Customer.js
Normal 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',
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
78
server/src/models/Vendor.js
Normal file
78
server/src/models/Vendor.js
Normal 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',
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user