Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

medici

Package Overview
Dependencies
Maintainers
2
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

medici - npm Package Compare versions

Comparing version 1.0.3 to 2.0.0

9

package.json
{
"name": "medici",
"version": "1.0.3",
"version": "2.0.0",
"description": "Simple double-entry accounting for Node + Mongoose",
"main": "src/index.js",
"scripts": {
"test": "mocha"
"test": "mocha && npm i mongoose@4 && mocha && npm i mongoose@5"
},

@@ -32,9 +32,10 @@ "files": [

"dependencies": {
"mongoose": "^4.11.7"
"mongoose": "^5.2.2"
},
"homepage": "https://github.com/koresar/medici",
"devDependencies": {
"mocha": "^3.5.0",
"mocha": "^5.2.0",
"prettier": "^1.13.7",
"should": "^11.2.1"
}
}
[![Build Status](https://travis-ci.org/koresar/medici.png?branch=master)](https://travis-ci.org/koresar/medici)
medici
======
# medici

@@ -12,3 +11,3 @@ Double-entry accounting system for nodejs + mongoose

Medici divides itself into "books", each of which store *journal entries* and their child *transactions*. The cardinal rule of double-entry accounting is that "everything must balance out to zero", and that rule is applied to every journal entry written to the book. If the transactions for a journal entry do not balance out to zero, the system will throw a new error with the message `INVALID JOURNAL`.
Medici divides itself into "books", each of which store _journal entries_ and their child _transactions_. The cardinal rule of double-entry accounting is that "for every debit entry, there must be a corresponding credit entry" which means "everything must balance out to zero", and that rule is applied to every journal entry written to the book. If the transactions for a journal entry do not balance out to zero, the system will throw a new error with the message `INVALID JOURNAL`.

@@ -26,6 +25,6 @@ Books simply represent the physical book in which you would record your transactions - on a technical level, the "book" attribute simply is added as a key-value pair to both the `Medici_Transactions` and `Medici_Journals` collection to allow you to have multiple books if you want to.

```js
const {book} = require('medici');
const { book } = require("medici");
// The first argument is the book name, which is used to determine which book the transactions and journals are queried from.
const myBook = new book('MyBook');
const myBook = new book("MyBook");
```

@@ -52,11 +51,13 @@

```js
myBook.balance({
account:'Assets:Accounts Receivable',
client:'Joe Blow'
}).then((balance) => {
myBook
.balance({
account: "Assets:Accounts Receivable",
client: "Joe Blow"
})
.then(balance => {
console.log("Joe Blow owes me", balance);
});
});
```
Note that the `meta` query parameters are on the same level as the default query parameters (account, _journal, start_date, end_date). Medici parses the query and automatically turns any values that do not match top-level schema properties into meta parameters.
Note that the `meta` query parameters are on the same level as the default query parameters (account, \_journal, start_date, end_date). Medici parses the query and automatically turns any values that do not match top-level schema properties into meta parameters.

@@ -68,12 +69,16 @@ ## Retrieving Transactions

```js
const startDate = moment().subtract('months', 1).toDate(); // One month ago
const startDate = moment()
.subtract("months", 1)
.toDate(); // One month ago
const endDate = new Date(); //today
myBook.ledger({
account: 'Income',
myBook
.ledger({
account: "Income",
start_date: startDate,
end_date: endDate
}).then((transactions) => {
})
.then(transactions => {
// Do something with the returned transaction documents
});
});
```

@@ -86,7 +91,7 @@

To void a journal entry, you can either call the `void(void_reason)` method on a Medici_Journal document, or use the `book.void(journal_id, void_reason)` method if you know the journal document's ID.
```js
myBook.void("123456", "I made a mistake").then(() => {
// Do something after voiding
})
// Do something after voiding
});
```

@@ -96,3 +101,2 @@

## Document Schema

@@ -104,18 +108,20 @@

JournalSchema = {
datetime: Date,
memo: {
type: String,
default: ''
},
_transactions: [{
type: Schema.Types.ObjectId,
ref: 'Medici_Transaction'
}],
book: String,
voided: {
type: Boolean,
default: false
},
void_reason: String
}
datetime: Date,
memo: {
type: String,
default: ""
},
_transactions: [
{
type: Schema.Types.ObjectId,
ref: "Medici_Transaction"
}
],
book: String,
voided: {
type: Boolean,
default: false
},
void_reason: String
};
```

@@ -127,21 +133,21 @@

TransactionSchema = {
credit: Number,
debit: Number,
meta: Schema.Types.Mixed,
datetime: Date,
account_path: [String],
accounts: String,
book: String,
memo: String,
_journal: {
type: Schema.Types.ObjectId,
ref:'Medici_Journal'
},
timestamp: Date,
voided: {
type: Boolean,
default: false
},
void_reason: String
}
credit: Number,
debit: Number,
meta: Schema.Types.Mixed,
datetime: Date,
account_path: [String],
accounts: String,
book: String,
memo: String,
_journal: {
type: Schema.Types.ObjectId,
ref: "Medici_Journal"
},
timestamp: Date,
voided: {
type: Boolean,
default: false
},
void_reason: String
};
```

@@ -151,4 +157,2 @@

### Customizing the Transaction document schema

@@ -162,25 +166,25 @@

MyTransactionSchema = {
_person: {
type:Schema.Types.ObjectId,
ref:'Person'
},
credit: Number,
debit: Number,
meta: Schema.Types.Mixed,
datetime: Date,
account_path: [String],
accounts: String,
book: String,
memo: String,
_journal: {
type: Schema.Types.ObjectId,
ref: 'Medici_Journal'
},
timestamp: Date,
voided: {
type: Boolean,
default: false
},
void_reason: String
}
_person: {
type: Schema.Types.ObjectId,
ref: "Person"
},
credit: Number,
debit: Number,
meta: Schema.Types.Mixed,
datetime: Date,
account_path: [String],
accounts: String,
book: String,
memo: String,
_journal: {
type: Schema.Types.ObjectId,
ref: "Medici_Journal"
},
timestamp: Date,
voided: {
type: Boolean,
default: false
},
void_reason: String
};
```

@@ -192,10 +196,15 @@

* **v1.0.0** _See [this PR](https://github.com/koresar/medici/pull/5) for more details_
* **BREAKING**: Dropped support of node.js v0.10, v0.12, v4, and io.js. Node.js >= v6 is supported only. This allowed to drop several production dependencies. Also, few bugs were automatically fixed.
* **BREAKING**: Upgraded `mongoose` to v4. This allows `medici` to be used with wider mongodb versions.
* Dropped production dependencies: `moment`, `q`, `underscore`.
* Dropped dev dependencies: `grunt`, `grunt-exec`, `grunt-contrib-coffee`, `grunt-sed`, `grunt-contrib-watch`, `semver`.
* No `.coffee` any more. Using node.js v6 compatible JavaScript only.
* There are no API changes.
* Fixed a [bug](https://github.com/koresar/medici/issues/4). Transaction meta data was not voided correctly.
* This module maintainer is now [koresar](https://github.com/koresar) instead of the original author [jraede](http://github.com/jraede).
- **v1.1.0**
- Support two mongoose versions simultaneously - v4 and v5.
- Support node.js v10.
- **v1.0.0** _See [this PR](https://github.com/koresar/medici/pull/5) for more details_
- **BREAKING**: Dropped support of node.js v0.10, v0.12, v4, and io.js. Node.js >= v6 is supported only. This allowed to drop several production dependencies. Also, few bugs were automatically fixed.
- **BREAKING**: Upgraded `mongoose` to v4. This allows `medici` to be used with wider mongodb versions.
- Dropped production dependencies: `moment`, `q`, `underscore`.
- Dropped dev dependencies: `grunt`, `grunt-exec`, `grunt-contrib-coffee`, `grunt-sed`, `grunt-contrib-watch`, `semver`.
- No `.coffee` any more. Using node.js v6 compatible JavaScript only.
- There are no API changes.
- Fixed a [bug](https://github.com/koresar/medici/issues/4). Transaction meta data was not voided correctly.
- This module maintainer is now [koresar](https://github.com/koresar) instead of the original author [jraede](http://github.com/jraede).

@@ -1,3 +0,3 @@

const mongoose = require('mongoose');
const entry = require('./entry');
const mongoose = require("mongoose");
const entry = require("./entry");

@@ -7,4 +7,4 @@ module.exports = class Book {

this.name = name;
this.transactionModel = mongoose.model('Medici_Transaction');
this.journalModel = mongoose.model('Medici_Journal');
this.transactionModel = mongoose.model("Medici_Transaction");
this.journalModel = mongoose.model("Medici_Journal");
}

@@ -30,3 +30,3 @@

for (let acct of account) {
accounts = acct.split(':');
accounts = acct.split(":");
const match = {};

@@ -38,5 +38,5 @@ for (i = 0; i < accounts.length; i++) {

}
parsed['$or'] = $or;
parsed["$or"] = $or;
} else {
accounts = account.split(':');
accounts = account.split(":");
for (i = 0; i < accounts.length; i++) {

@@ -50,3 +50,3 @@ parsed[`account_path.${i}`] = accounts[i];

if (query._journal) {
parsed['_journal'] = query._journal;
parsed["_journal"] = query._journal;
}

@@ -57,3 +57,3 @@

end_date = new Date(parseInt(query.end_date));
parsed['datetime'] = {
parsed["datetime"] = {
$gte: start_date,

@@ -65,6 +65,6 @@ $lte: end_date

} else if (query.start_date) {
parsed['datetime'] = {$gte: new Date(parseInt(query.start_date))};
parsed["datetime"] = { $gte: new Date(parseInt(query.start_date)) };
delete query.start_date;
} else if (query.end_date) {
parsed['datetime'] = {$lte: new Date(parseInt(query.end_date))};
parsed["datetime"] = { $lte: new Date(parseInt(query.end_date)) };
delete query.end_date;

@@ -78,3 +78,3 @@ }

// If it starts with a _ assume it's a reference
if (key.substr(0, 1) === '_' && val instanceof String) {
if (key.substr(0, 1) === "_" && val instanceof String) {
val = mongoose.Types.ObjectId(val);

@@ -85,3 +85,3 @@ }

// Assume *_id is an OID
if (key.indexOf('_id') > 0) {
if (key.indexOf("_id") > 0) {
val = mongoose.Types.ObjectId(val);

@@ -114,10 +114,10 @@ }

query = this.parseQuery(query);
const match = {$match: query};
const match = { $match: query };
const project = {
$project: {
debit: '$debit',
credit: '$credit',
datetime: '$datetime',
timestamp: '$timestamp'
debit: "$debit",
credit: "$credit",
datetime: "$datetime",
timestamp: "$timestamp"
}

@@ -127,8 +127,8 @@ };

$group: {
_id: '1',
_id: "1",
credit: {
$sum: '$credit'
$sum: "$credit"
},
debit: {
$sum: '$debit'
$sum: "$debit"
},

@@ -141,3 +141,3 @@ count: {

if (pagination) {
const skip = {$skip: (pagination.page - 1) * pagination.perPage};
const skip = { $skip: (pagination.page - 1) * pagination.perPage };
const sort = {

@@ -150,37 +150,37 @@ $sort: {

return this.transactionModel
.aggregate(match, project, sort, skip, group)
.then(function (result) {
result = result.shift();
if (!result) {
.aggregate([match, project, sort, skip, group])
.then(function(result) {
result = result.shift();
if (!result) {
return {
balance: 0,
notes: 0
};
}
const total = result.credit - result.debit;
return {
balance: 0,
notes: 0
balance: total,
notes: result.count
};
}
const total = result.credit - result.debit;
return {
balance: total,
notes: result.count
};
});
});
} else {
return this.transactionModel
.aggregate(match, project, group)
.then(function (result) {
result = result.shift();
if (!result) {
.aggregate([match, project, group])
.then(function(result) {
result = result.shift();
if (!result) {
return {
balance: 0,
notes: 0
};
}
const total = result.credit - result.debit;
return {
balance: 0,
notes: 0
balance: total,
notes: result.count
};
}
const total = result.credit - result.debit;
return {
balance: total,
notes: result.count
};
});
});
}

@@ -206,7 +206,6 @@ }

if (pagination) {
return this.transactionModel.count(query)
.then(count => {
q
.skip((pagination.page - 1) * pagination.perPage)
.limit(pagination.perPage);
return this.transactionModel.count(query).then(count => {
q.skip((pagination.page - 1) * pagination.perPage).limit(
pagination.perPage
);
q.sort({

@@ -222,4 +221,3 @@ datetime: -1,

return q.exec()
.then(function (results) {
return q.exec().then(function(results) {
return {

@@ -242,4 +240,3 @@ results,

return q.exec()
.then(function (results) {
return q.exec().then(function(results) {
return {

@@ -255,4 +252,4 @@ results,

return this.journalModel
.findById(journal_id)
.then(journal => journal.void(this, reason));
.findById(journal_id)
.then(journal => journal.void(this, reason));
}

@@ -262,22 +259,22 @@

return this.transactionModel
.find({book: this.name})
.distinct('accounts')
.then(function (results) {
// Make array
const final = [];
for (let result of results) {
const paths = result.split(':');
const prev = [];
for (let acct of paths) {
prev.push(acct);
final.push(prev.join(':'));
.find({ book: this.name })
.distinct("accounts")
.then(function(results) {
// Make array
const final = [];
for (let result of results) {
const paths = result.split(":");
const prev = [];
for (let acct of paths) {
prev.push(acct);
final.push(prev.join(":"));
}
}
}
return Array.from(new Set(final)); // uniques
})
.catch(err => {
console.error(err);
throw err;
});
return Array.from(new Set(final)); // uniques
})
.catch(err => {
console.error(err);
throw err;
});
}
};

@@ -8,3 +8,3 @@ module.exports = class Entry {

this.book = book;
const {journalModel} = this.book;
const { journalModel } = this.book;
this.journal = new journalModel();

@@ -34,8 +34,8 @@ this.journal.memo = memo;

amount = parseFloat(amount);
if (typeof account_path === 'string') {
account_path = account_path.split(':');
if (typeof account_path === "string") {
account_path = account_path.split(":");
}
if (account_path.length > 3) {
throw 'Account path is too deep (maximum 3)';
throw "Account path is too deep (maximum 3)";
}

@@ -45,3 +45,3 @@

account_path,
accounts: account_path.join(':'),
accounts: account_path.join(":"),
credit: amount,

@@ -76,7 +76,7 @@ debit: 0.0,

amount = parseFloat(amount);
if (typeof account_path === 'string') {
account_path = account_path.split(':');
if (typeof account_path === "string") {
account_path = account_path.split(":");
}
if (account_path.length > 3) {
throw 'Account path is too deep (maximum 3)';
throw "Account path is too deep (maximum 3)";
}

@@ -86,3 +86,3 @@

account_path,
accounts: account_path.join(':'),
accounts: account_path.join(":"),
credit: 0.0,

@@ -144,16 +144,19 @@ debit: amount,

if (total > 0 || total < 0) {
const err = new Error('INVALID_JOURNAL');
const err = new Error("INVALID_JOURNAL");
err.code = 400;
console.error('Journal is invalid. Total is:', total);
console.error("Journal is invalid. Total is:", total);
return Promise.reject(err);
} else {
return Promise.all(this.transactions.map(tx => this.saveTransaction(tx)))
.then(() => {
return this.journal.save()
.then(() => this.journal).catch(err => {
this.book.transactionModel.remove({
_journal: this.journal._id
return Promise.all(
this.transactions.map(tx => this.saveTransaction(tx))
).then(() => {
return this.journal
.save()
.then(() => this.journal)
.catch(err => {
this.book.transactionModel.remove({
_journal: this.journal._id
});
throw new Error(`Failure to save journal: ${err.message}`);
});
throw new Error(`Failure to save journal: ${err.message}`);
});
});

@@ -160,0 +163,0 @@ }

@@ -1,4 +0,4 @@

const Book = require('./book');
const mongoose = require('mongoose');
const {Schema} = mongoose;
const Book = require("./book");
const mongoose = require("mongoose");
const { Schema } = mongoose;

@@ -8,3 +8,3 @@ // This lets you register your own schema before including Medici. Useful if you want to store additional information

try {
mongoose.model('Medici_Transaction');
mongoose.model("Medici_Transaction");
} catch (error) {

@@ -22,3 +22,3 @@ const transactionSchema = new Schema({

type: Schema.Types.ObjectId,
ref: 'Medici_Journal'
ref: "Medici_Journal"
},

@@ -41,3 +41,3 @@ timestamp: {

});
mongoose.model('Medici_Transaction', transactionSchema);
mongoose.model("Medici_Transaction", transactionSchema);
}

@@ -50,3 +50,3 @@

try {
journalSchema = mongoose.model('Medici_Journal');
journalSchema = mongoose.model("Medici_Journal");
} catch (error) {

@@ -57,3 +57,3 @@ journalSchema = new Schema({

type: String,
default: ''
default: ""
},

@@ -63,3 +63,3 @@ _transactions: [

type: Schema.Types.ObjectId,
ref: 'Medici_Transaction'
ref: "Medici_Transaction"
}

@@ -79,5 +79,5 @@ ],

journalSchema.methods.void = function (book, reason) {
journalSchema.methods.void = function(book, reason) {
if (this.voided === true) {
return Promise.reject(new Error('Journal already voided'));
return Promise.reject(new Error("Journal already voided"));
}

@@ -88,3 +88,3 @@

if (!reason) {
this.void_reason = '';
this.void_reason = "";
} else {

@@ -96,83 +96,83 @@ this.void_reason = reason;

return mongoose
.model('Medici_Transaction')
.findByIdAndUpdate(trans_id, {
voided: true,
void_reason: this.void_reason
})
.catch(err => {
console.error('Failed to void transaction:', err);
throw err;
});
.model("Medici_Transaction")
.findByIdAndUpdate(trans_id, {
voided: true,
void_reason: this.void_reason
})
.catch(err => {
console.error("Failed to void transaction:", err);
throw err;
});
};
return Promise.all(this._transactions.map(voidTransaction))
.then(transactions => {
let newMemo;
if (this.void_reason) {
newMemo = this.void_reason;
} else {
// It's either VOID, UNVOID, or REVOID
if (this.memo.substr(0, 6) === '[VOID]') {
newMemo = this.memo.replace('[VOID]', '[UNVOID]');
} else if (this.memo.substr(0, 8) === '[UNVOID]') {
newMemo = this.memo.replace('[UNVOID]', '[REVOID]');
} else if (this.memo.substr(0, 8) === '[REVOID]') {
newMemo = this.memo.replace('[REVOID]', '[UNVOID]');
return Promise.all(this._transactions.map(voidTransaction)).then(
transactions => {
let newMemo;
if (this.void_reason) {
newMemo = this.void_reason;
} else {
newMemo = `[VOID] ${this.memo}`;
// It's either VOID, UNVOID, or REVOID
if (this.memo.substr(0, 6) === "[VOID]") {
newMemo = this.memo.replace("[VOID]", "[UNVOID]");
} else if (this.memo.substr(0, 8) === "[UNVOID]") {
newMemo = this.memo.replace("[UNVOID]", "[REVOID]");
} else if (this.memo.substr(0, 8) === "[REVOID]") {
newMemo = this.memo.replace("[REVOID]", "[UNVOID]");
} else {
newMemo = `[VOID] ${this.memo}`;
}
}
}
// Ok now create an equal and opposite journal
const entry = book.entry(newMemo, null, this._id);
const valid_fields = [
'credit',
'debit',
'account_path',
'accounts',
'datetime',
'book',
'memo',
'timestamp',
'voided',
'void_reason',
'_original_journal'
];
// Ok now create an equal and opposite journal
const entry = book.entry(newMemo, null, this._id);
const valid_fields = [
"credit",
"debit",
"account_path",
"accounts",
"datetime",
"book",
"memo",
"timestamp",
"voided",
"void_reason",
"_original_journal"
];
function processMetaField(key, val, meta) {
if (key === '_id' || key === '_journal') {
} else if (valid_fields.indexOf(key) === -1) {
return meta[key] = val;
function processMetaField(key, val, meta) {
if (key === "_id" || key === "_journal") {
} else if (valid_fields.indexOf(key) === -1) {
return (meta[key] = val);
}
}
}
for (let trans of transactions) {
trans = trans.toObject();
const meta = {};
for (let trans of transactions) {
trans = trans.toObject();
const meta = {};
Object.keys(trans).forEach(key => {
const val = trans[key];
if (key === 'meta') {
Object.keys(trans['meta']).forEach(keyMeta => {
processMetaField(keyMeta, trans['meta'][keyMeta], meta);
});
} else {
processMetaField(key, val, meta);
Object.keys(trans).forEach(key => {
const val = trans[key];
if (key === "meta") {
Object.keys(trans["meta"]).forEach(keyMeta => {
processMetaField(keyMeta, trans["meta"][keyMeta], meta);
});
} else {
processMetaField(key, val, meta);
}
});
if (trans.credit) {
entry.debit(trans.account_path, trans.credit, meta);
}
});
if (trans.debit) {
entry.credit(trans.account_path, trans.debit, meta);
}
}
if (trans.credit) {
entry.debit(trans.account_path, trans.credit, meta);
}
if (trans.debit) {
entry.credit(trans.account_path, trans.debit, meta);
}
return entry.commit();
}
return entry.commit();
});
);
};
journalSchema.pre('save', function (next) {
if (!(this.isModified('approved') && this.approved === true)) {
journalSchema.pre("save", function(next) {
if (!(this.isModified("approved") && this.approved === true)) {
return next();

@@ -182,14 +182,16 @@ }

return mongoose
.model('Medici_Transaction')
.find({_journal: this._id})
.then((transactions) => Promise.all(transactions.map(tx => {
tx.approved = true;
return tx.save();
}))
.then(() => next())
);
.model("Medici_Transaction")
.find({ _journal: this._id })
.then(transactions =>
Promise.all(
transactions.map(tx => {
tx.approved = true;
return tx.save();
})
).then(() => next())
);
});
mongoose.model('Medici_Journal', journalSchema);
mongoose.model("Medici_Journal", journalSchema);
}
module.exports = {book: Book};
module.exports = { book: Book };
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc