diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2a90d1f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +## 0.0.7 + + - Adding nomenclature + +## 0.0.1 - 0.0.6 + + - Base billing structure (charges, modifiers and payments) \ No newline at end of file diff --git a/README.md b/README.md index da14387..6f5fde1 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ bill.modifiers //Modifier collection bill.payments //Payment collection bill.total //The whole number or amount (charges and modifiers) bill.balance //Bill remainder (total - payments) +bill.toJson() //A JSON object (without circular references) ``` ### Billing collections @@ -71,9 +72,49 @@ bill.balance //Bill remainder (total - payments) ### Validations - .isValid // check validity + boolean .isValid // check validity .errors // errors format [{ propertyName: { validationName: 'Human readable error' }}] +## Using the nomenclature ([samples/node/usingNomenclature.js](samples/node/usingNomenclature.js)) +```javascript +var billing = require('../../').Billing; + +billing.config({ + nomenclature: { + taxGroups: [ + { id: 1, name: '20%', percentRatio: 0.2 }, + { id: 2, name: '9%', percentRatio: 0.09 } + ], + departments: [ + { id: 1, name: 'Food', taxGroupId: 1 }, + { id: 2, name: 'Accommodation', taxGroupId: 2 } + ], + plus: [ + { id: 1, name: 'Pizza', departmentId: 1, price: 20.5 }, + { id: 2, name: 'Steak', departmentId: 1, price: 30.2 }, + { id: 3, name: 'Room', departmentId: 2, price: 200 } + ], + paymentTypes: [ + { id: 1, name: 'Cash', isCash: true, isFiscal: true }, + { id: 2, name: 'Card', isCash: false, isFiscal: true }, + { id: 3, name: 'External', isCash: false, isFiscal: false } + ] + } +}); + +var bill = billing.bills.new(); + +bill.charges.new({ pluId: 1 }); // -> { qty: 1, price: 20.5, name: 'Pizza', taxRatio: 0.2 } +bill.charges.new({ qty: 2, price: 150, departmentId: 2 }); +bill.payments.new({ paymentTypeId: 1 }); // -> { name: 'Cash', value: 20.5, isCash: true, isFiscal: true } + +bill.toJson(); // -> { +// charges: +// [ { qty: 1, price: 20.5, name: 'Pizza', taxRatio: 0.2 }, +// { qty: 2, price: 150, name: 'Accommodation', taxRatio: 0.09 } ], +// payments: [ { name: 'Cash', value: 320.5, isCash: true, isFiscal: true } ] } +``` + ## Build `npm run build` - Generate node library. (/lib/*) diff --git a/karma.conf.js b/karma.conf.js index d1c9bb3..1f8e2a0 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -16,7 +16,7 @@ module.exports = function (config) { "**/*.ts": ["karma-typescript"] }, - reporters: ["dots", "karma-typescript"], + reporters: ["mocha", "karma-typescript"], browsers: ["PhantomJS"], diff --git a/package.json b/package.json index ec0be2d..e6efa24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "billing", - "version": "0.0.6", + "version": "0.0.7", "description": "Billing Module Js", "author": "AlexV", "repository": { @@ -29,6 +29,7 @@ "karma": "^1.2.0", "karma-cli": "^1.0.1", "karma-jasmine": "^1.0.2", + "karma-mocha-reporter": "^2.2.0", "karma-phantomjs-launcher": "^1.0.2", "karma-typescript": "^2.0.5", "source-map": "^0.5.6", diff --git a/samples/node/usingNomenclature.js b/samples/node/usingNomenclature.js new file mode 100644 index 0000000..8a7eb6f --- /dev/null +++ b/samples/node/usingNomenclature.js @@ -0,0 +1,32 @@ +var billing = require('../../').Billing; + +billing.config({ + nomenclature: { + taxGroups: [ + { id: 1, name: '20%', percentRatio: 0.2 }, + { id: 2, name: '9%', percentRatio: 0.09 } + ], + departments: [ + { id: 1, name: 'Food', taxGroupId: 1 }, + { id: 2, name: 'Accommodation', taxGroupId: 2 } + ], + plus: [ + { id: 1, name: 'Pizza', departmentId: 1, price: 20.5 }, + { id: 2, name: 'Steak', departmentId: 1, price: 30.2 }, + { id: 3, name: 'Room', departmentId: 2, price: 200 } + ], + paymentTypes: [ + { id: 1, name: 'Cash', isCash: true, isFiscal: true }, + { id: 2, name: 'Card', isCash: false, isFiscal: true }, + { id: 3, name: 'External', isCash: false, isFiscal: false } + ] + } +}); + +var bill = billing.bills.new(); + +bill.charges.new({ pluId: 1 }); // -> { qty: 1, price: 20.5, name: 'Pizza', taxRatio: 0.2 } +bill.charges.new({ qty: 2, price: 150, departmentId: 2 }); +bill.payments.new({ paymentTypeId: 1 }); // -> { name: 'Cash', value: 20.5, isCash: true, isFiscal: true } + +console.log(bill.toJson()); \ No newline at end of file diff --git a/src/billing/bill.ts b/src/billing/bill.ts index 17250bd..797056e 100644 --- a/src/billing/bill.ts +++ b/src/billing/bill.ts @@ -7,7 +7,10 @@ import { ChargesCollection } from './charge'; import { Modifier, ModifiersCollection } from './modifier'; import { PaymentsCollection } from './payment'; import { ValidationModel } from './concerns/validations'; +import { Operator } from './nomenclature'; +import { IBillAttributes } from './interface'; +export declare type GlobalModifier = Modifier; /** * * @@ -18,7 +21,7 @@ import { ValidationModel } from './concerns/validations'; export class Bill extends ValidationModel { isSaved: boolean; - constructor(attributes :any = {}) { + constructor(attributes :IBillAttributes = {}) { super(); } @@ -34,6 +37,13 @@ export class Bill extends ValidationModel { * @type {ModifiersCollection} */ modifiers :ModifiersCollection = new ModifiersCollection(this); + /** + * + * + * @type {number} + * @memberOf Bill + */ + operatorId :number; /** * * @@ -41,6 +51,12 @@ export class Bill extends ValidationModel { */ payments :PaymentsCollection = new PaymentsCollection(this); + get modifier() :GlobalModifier { + for (let m of this.modifiers) { + if (!(m).charge) return m; + } + } + /** * * @@ -67,6 +83,20 @@ export class Bill extends ValidationModel { return this.isSaved = success; } + toJson(useNomenclatureIds = false) :any { + if (this.isValid) { + let json = {}; + if (this.charges.length) json['charges'] = this.charges.toJson(useNomenclatureIds); + if (this.payments.length) json['payments'] = this.payments.toJson(useNomenclatureIds); + if (this.modifier) json['modifier'] = this.modifier.toJson(); + return json; + } + } + + get operator() { + if (this.operatorId) return Operator.find(this.operatorId); + } + static new(attributes :any = {}) :Bill { return new Bill(attributes); } diff --git a/src/billing/charge/charge.spec.ts b/src/billing/charge/charge.spec.ts index bcd49c0..93b4b10 100644 --- a/src/billing/charge/charge.spec.ts +++ b/src/billing/charge/charge.spec.ts @@ -9,6 +9,8 @@ import { Charge } from './index'; import { Modifier } from '../modifier'; import { Bill } from '../bill'; +import * as Nomenclature from '../nomenclature'; + describe('Charge', () => { it('default values', () => { let bill = new Bill(); @@ -194,6 +196,59 @@ describe('Charge', () => { }).not.toThrowError(ReferenceError); }); + it('set description', function() { + let charge = new Charge({ price: 1 }); + expect(charge.description).toEqual(''); + charge.description = 'Fresh'; + expect(charge.description).toEqual('Fresh'); + }); + + it('set price', function() { + let charge = new Charge({ pluId: 1 }); + expect(charge.price).toEqual(0); + charge.price = 1.26; + expect(charge.price).toEqual(1.26); + }); + + it('set modifier', function() { + let charge = new Charge({ price: 1 }); + let modifier = new Modifier({ fixedValue: 1, charge: charge }); + charge.modifier = modifier; + expect(charge.finalValue).toEqual(2); + }); + + it('set chross bill modifier should raise exception', function() { + let bill = new Bill(); + let modifier = bill.modifiers.new({ fixedValue: 1 }); + let bill2 = new Bill(); + expect(()=> { bill2.charges.new({ price: 1, modifier: modifier }) }).toThrowError(ReferenceError); + }); + + it('set same bill modifier should pass', function() { + let bill = new Bill(); + let modifier = bill.modifiers.new({ fixedValue: 1 }); + expect(()=> { bill.charges.new({ price: 1, modifier: modifier }) }).not.toThrowError(); + }); + + it('modify nothing', function() { + let charge = new Charge({ price: 1 }); + let modifier = charge.modify(); + expect(modifier instanceof Modifier).toBeTruthy(); + expect(charge.finalValue).toEqual(1); + }); + + it('update nothing', function() { + let charge = new Charge({ price: 1 }); + expect(charge.update()).toBeTruthy(); + }); + + it('set modifier should adopt charge', function() { + let charge = new Charge({ price: 1 }); + let modifier = new Modifier({ fixedValue: 1 }); + charge.modifier = modifier; + expect(modifier.charge).toEqual(charge); + }); + describe('validations', function() { it('require bill', function() { let charge = new Charge({ price: 1 }); @@ -206,5 +261,140 @@ describe('Charge', () => { expect(charge.isValid).toBeFalsy(); expect(charge.errors.messages).toContain('Price must be greater than 0'); }); + + it('invalid modifier', function() { + let charge = new Charge({ price: 1 }); + charge.modify(); + expect(charge.isValid).toBeFalsy(); + expect(charge.errors.messages).toContain('Modifier is invalid'); + }); + }); + + describe('nomenclature', function() { + beforeAll(function() { + Nomenclature.init({ + plus: [ + { id: 1, code: '1', name: 'Test Plu', description: 'Descr', departmentId: 1, price: 1.5 }, + { id: 2, code: '2', name: 'Test Plu2', departmentId: 1, price: 1.5 } + ], + taxGroups: [ + { id: 1, code: '1', name: '20%', percentRatio: 0.2 }, + { id: 2, code: '2', name: '9%', percentRatio: 0.09 } + ], + }); + }); + + it('inherit attributes from Plu', function() { + let bill = new Bill(); + let charge = bill.charges.new({ pluId: 1 }); + expect(charge.isValid).toBeTruthy(); + expect(charge.name).toEqual('Test Plu'); + expect(charge.description).toEqual('Descr'); + expect(charge.price).toEqual(1.5); + }); + + it('inherit attributes from TaxGroup', function() { + let charge = new Charge({ taxGroupId: 1 }); + expect(charge.taxRatio).toEqual(0.2); + }); + + it('own attributes takes precedence over relations', function() { + let charge = new Charge({ name: 'My Name', description: 'My Descriprion', price: 45, taxRatio: 0.09 }); + charge.pluId = 1; + expect(charge.plu.name).toEqual('Test Plu'); + expect(charge.name).toEqual('My Name'); + expect(charge.description).toEqual('My Descriprion'); + expect(charge.price).toEqual(45); + charge.taxGroupId = 1; + expect(charge.taxGroup.percentRatio).toEqual(0.2); + expect(charge.taxRatio).toEqual(0.09); + }); + + it('missing nomenclature should not throw exteptions', function() { + let charge = new Charge({ pluId: 2 }); + expect(()=> { charge.taxRatio; }).not.toThrow(); + expect(charge.department).toBeUndefined(); + }); + + it('direct department', function() { + Nomenclature.init({ + departments: [ + { id: 1, code: '1', name: 'D', taxGroupId: 1 }, + { id: 2, code: '2', name: 'E', taxGroupId: 2 } + ] + }); + let charge = new Charge({ price: 3 }); + expect(charge.taxRatio).toEqual(0); + charge.departmentId = 1; + expect(charge.department.taxRatio).toEqual(0.2); + expect(charge.taxRatio).toEqual(0.2); + charge.update({ departmentId: 2 }); + expect(charge.taxRatio).toEqual(0.09); + }); + + it('update nomenclature direct', function() { + let charge = new Charge({ price: 3 }); + charge.update({ plu: Nomenclature.Plu.find(1) }); + expect(charge.pluId).toEqual(1); + charge.update({ taxGroup: Nomenclature.TaxGroup.find(1) }); + expect(charge.taxGroupId).toEqual(1); + charge.update({ department: Nomenclature.Department.find(2) }); + expect(charge.departmentId).toEqual(2); + }); + }); + + describe('toJson', function() { + let bill :Bill; + let charge :Charge; + beforeEach(function() { + Nomenclature.init({ + plus: [ + { id: 1, code: '1', name: 'Test Plu', description: 'Descr', departmentId: 1, price: 1.5 } + ], + taxGroups: [ + { id: 1, code: '1', name: '20%', percentRatio: 0.2 } + ], + departments: [ + { id: 1, code: '1', name: 'D', taxGroupId: 1 } + ] + }); + bill = new Bill(); + charge = bill.charges.new({ pluId: 1, modifier: { fixedValue: 1 } }); + }); + + it('invalid', function() { + let charge = new Charge(); + expect(charge.toJson()).toBeUndefined(); + }); + + it('minimal', function() { + let charge = bill.charges.new({ price: 1 }); + expect(charge.toJson()).toEqual({ qty: 1, price: 1, name: '' }); + expect(charge.toJson(true)).toEqual({ qty: 1, price: 1, name: '' }); + }); + + it('without nomenclature', function() { + expect(charge.toJson()).toEqual({ + qty: 1, price: 1.5, name: 'Test Plu', description: 'Descr', taxRatio: 0.2, + modifier: { fixedValue: 1 } + }); + }); + + it('use nomenclature ids', function() { + expect(charge.toJson(true)).toEqual({ + qty: 1, price: 1.5, name: 'Test Plu', description: 'Descr', taxRatio: 0.2, + modifier: { fixedValue: 1 }, pluId: 1 + }); + charge.departmentId = 1; + expect(charge.toJson(true)).toEqual({ + qty: 1, price: 1.5, name: 'Test Plu', description: 'Descr', taxRatio: 0.2, + modifier: { fixedValue: 1 }, pluId: 1, departmentId: 1 + }); + charge.taxGroupId = 1; + expect(charge.toJson(true)).toEqual({ + qty: 1, price: 1.5, name: 'Test Plu', description: 'Descr', taxRatio: 0.2, + modifier: { fixedValue: 1 }, pluId: 1, departmentId: 1, taxGroupId: 1 + }); + }); }); }); \ No newline at end of file diff --git a/src/billing/charge/collection.ts b/src/billing/charge/collection.ts index 38983cd..a98f236 100644 --- a/src/billing/charge/collection.ts +++ b/src/billing/charge/collection.ts @@ -71,7 +71,7 @@ export class ChargesCollection extends BillCollection { sum() :number { let sum = 0; this.forEach((charge)=> { - sum += ( charge).value; + sum += (charge).value; }); return sum; } @@ -84,7 +84,7 @@ export class ChargesCollection extends BillCollection { finalSum() :number { let sum = 0; this.forEach((charge)=> { - sum += ( charge).finalValue; + sum += (charge).finalValue; }); return sum; } diff --git a/src/billing/charge/index.ts b/src/billing/charge/index.ts index e4a1684..26ab7af 100644 --- a/src/billing/charge/index.ts +++ b/src/billing/charge/index.ts @@ -8,6 +8,8 @@ import { Modifier } from '../modifier'; import { IChargeAttributes } from './interface'; import { IModifierAttributes } from '../modifier/interface'; +import { Plu, TaxGroup, Department } from '../nomenclature'; + export declare type ModifierOrAttributes = Modifier | IModifierAttributes; /** @@ -18,41 +20,65 @@ export declare type ModifierOrAttributes = Modifier | IModifierAttributes; * @extends {BillItem} */ export class Charge extends BillItem { - /** - * - * - * @type {Modifier} - */ + private _modifier :Modifier; - /** - * - * - * @type {string} - */ - name :string = ''; - /** - * - * - * @type {string} - */ - description :string; - /** - * - * - * @type {number} - */ - price :number = 0; - /** - * - * - * @type {number} - */ + + private _name :string; + + get name() :string { + let name :string; + if (this._name) name = this._name; + if (!name) if (this.plu) name = this.plu.name; + if (!name) if (this.department) name = this.department.name; + return name || ''; + } + set name(value :string) { + this._name = value; + } + + private _description :string; + get description() :string { + return this._description ? this._description : (this.plu ? this.plu.description : ''); + } + set description(value :string) { + this._description = value; + } + + private _price :number; + get price() :number { + return this._price ? this._price : (this.plu ? this.plu.price : 0); + } + set price(value :number) { + this._price = value; + } + + private _taxRatio :number; + get taxRatio() :number { + let taxRercentRatio :number; + if (this._taxRatio) taxRercentRatio = this._taxRatio; + if (!taxRercentRatio) + if (this.taxGroup) taxRercentRatio = this.taxGroup.percentRatio; + if (!taxRercentRatio) + if (this.department) taxRercentRatio = this.department.taxRatio; + if (!taxRercentRatio) + if (this.plu) taxRercentRatio = this.plu.taxRatio + return taxRercentRatio || 0; + } + qty :number = 1; + pluId :number; + + taxGroupId :number; + + departmentId :number; + /** * Creates an instance of Charge. * * @param {IChargeAttributes} [attributes={}] + * + * @memberOf Charge */ constructor(attributes: IChargeAttributes = {}) { super(attributes.bill); @@ -62,21 +88,21 @@ export class Charge extends BillItem { get modifier() :Modifier { return this._modifier; } + set modifier(modifier :Modifier) { + let modifierBill = modifier.bill; + if (modifierBill) { + if (!this.bill) this._bill = modifierBill; + else if (this.bill !== modifierBill) throw new ReferenceError('Charge with modifier belonging to another bill.'); + } else modifier.update({ bill: this.bill }); + if (modifier.charge !== this) modifier.charge = this; + this._modifier = modifier; + if (this.bill && !~this.bill.charges.indexOf(this)) this.bill.charges.add(this); + } - /** - * - * - * @returns {number} - */ get value() :number { return (this.qty * this.price); } - /** - * - * - * @returns {number} - */ get finalValue() :number { return this.modifier ? this.value + this.modifier.value : this.value; } @@ -113,11 +139,6 @@ export class Charge extends BillItem { return true; }; - /** - * - * - * @returns {Boolean} - */ delete():Boolean { if (this.bill) this.bill.charges.remove(this); return delete this; @@ -126,19 +147,19 @@ export class Charge extends BillItem { update(attributes: IChargeAttributes = {}) :boolean { if (attributes.bill) this._bill = attributes.bill; if (attributes.name) this.name = attributes.name; - if (attributes.description) this.description = attributes.description; - if (attributes.price) this.price = attributes.price; + if (attributes.description) this._description = attributes.description; + if (attributes.price) this._price = attributes.price; if (attributes.qty) this.qty = attributes.qty; + if (attributes.plu) this.plu = attributes.plu; + if (attributes.pluId) this.pluId = attributes.pluId; + if (attributes.taxGroup) this.taxGroup = attributes.taxGroup; + if (attributes.taxGroupId) this.taxGroupId = attributes.taxGroupId; + if (attributes.taxRatio) this._taxRatio = attributes.taxRatio; + if (attributes.department) this.department = attributes.department; + if (attributes.departmentId) this.departmentId = attributes.departmentId; if (attributes.modifier) { if (attributes.modifier instanceof Modifier) { - let modifier = attributes.modifier; - let modifierBill = modifier.bill; - if (modifierBill) { - if (!this.bill) this._bill = modifierBill; - else if (this.bill !== modifierBill) throw new ReferenceError('Charge with modifier belonging to another bill.'); - } else modifier.update({ bill: this.bill }); - if (modifier.charge !== this) modifier.charge = this; - this._modifier = modifier; + this.modifier = attributes.modifier; } else { if (attributes.modifier.bill) { if (!this.bill) this._bill = attributes.modifier.bill; @@ -146,12 +167,52 @@ export class Charge extends BillItem { } else attributes.modifier.bill = this.bill; attributes.modifier.charge = this; this._modifier = new Modifier(attributes.modifier); + if (this.bill && !~this.bill.charges.indexOf(this)) this.bill.charges.add(this); } - if (this.bill) this.bill.modifiers.add(this.modifier); } if (this.bill && !~this.bill.charges.indexOf(this)) this.bill.charges.add(this); return true; } + + toJson(useNomenclatureIds = false) :any { + if (this.isValid) { + let json = { + qty: this.qty + }; + json['price'] = this.price; + json['name'] = this.name; + if (this.description) json['description'] = this.description; + if (this.modifier) json['modifier'] = this.modifier.toJson(); + if (this.taxRatio) json['taxRatio'] = this.taxRatio; + if (useNomenclatureIds) { + if (this.pluId) json['pluId'] = this.pluId; + if (this.departmentId) json['departmentId'] = this.departmentId; + if (this.taxGroupId) json['taxGroupId'] = this.taxGroupId; + } + return json; + } + } + + get plu() :Plu { + if (this.pluId) return Plu.find(this.pluId); + } + set plu(plu :Plu) { + this.pluId = plu.id; + } + + get taxGroup() { + if (this.taxGroupId) return TaxGroup.find(this.taxGroupId); + } + set taxGroup(taxGroup :TaxGroup) { + this.taxGroupId = taxGroup.id; + } + + get department() { + if (this.departmentId) return Department.find(this.departmentId); + } + set department(department :Department) { + this.departmentId = department.id; + } } Charge.validates('bill', { presence: true }); diff --git a/src/billing/charge/interface.ts b/src/billing/charge/interface.ts index 738f545..04b9ae8 100644 --- a/src/billing/charge/interface.ts +++ b/src/billing/charge/interface.ts @@ -6,6 +6,7 @@ import { Bill } from '../bill'; import { Modifier } from '../modifier'; import { IModifierAttributes } from '../modifier/interface'; +import { Plu, TaxGroup, Department } from '../nomenclature'; export type ModifierObject = Modifier | IModifierAttributes; @@ -21,35 +22,66 @@ export interface IChargeAttributes { * * @type {Bill} */ - bill?: Bill; + bill ?:Bill; /** * * * @type {string} */ - name?: string; + name ?:string; /** * * * @type {number} */ - qty?: number; + qty ?:number; /** * * * @type {number} */ - price?: number; + price ?:number; /** * * * @type {string} */ - description?: string; + description ?:string; + /** + * + * + * @type {number} + * @memberOf IChargeAttributes + */ + taxRatio ?:number; /** * * * @type {ModifierObject} */ - modifier?: ModifierObject; + modifier ?:ModifierObject; + /** + * + * + * @type {number} + * @memberOf IChargeAttributes + */ + pluId ?:number; + plu ?:Plu; + /** + * + * + * @type {number} + * @memberOf IChargeAttributes + */ + taxGroupId ?:number; + taxGroup ?:TaxGroup; + /** + * + * + * @type {number} + * @memberOf IChargeAttributes + */ + departmentId ?:number; + department ?:Department; } \ No newline at end of file diff --git a/src/billing/collection.ts b/src/billing/collection.ts new file mode 100644 index 0000000..6aa2447 --- /dev/null +++ b/src/billing/collection.ts @@ -0,0 +1,15 @@ +// Copyright (c) 2016 AlexV +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +import { Bill } from './bill'; +import { IBillAttributes } from './interface'; + +export class Bills extends Array { + new(attributes: IBillAttributes = {}) :Bill { + let bill = new Bill(attributes); + if (!~this.indexOf(bill))this.push(bill); + return bill; + } +} \ No newline at end of file diff --git a/src/billing/concerns/billCollection.ts b/src/billing/concerns/billCollection.ts index 8e38bfa..c2c7f34 100644 --- a/src/billing/concerns/billCollection.ts +++ b/src/billing/concerns/billCollection.ts @@ -72,4 +72,8 @@ export abstract class BillCollection extends Array { }); return success; } + + toJson(useNomenclatureIds = false) :any { + return this.map((item)=> item.toJson(useNomenclatureIds) ); + } } \ No newline at end of file diff --git a/src/billing/concerns/billItem.ts b/src/billing/concerns/billItem.ts index 24b0b78..54f24c5 100644 --- a/src/billing/concerns/billItem.ts +++ b/src/billing/concerns/billItem.ts @@ -68,4 +68,6 @@ export abstract class BillItem extends ValidationModel { } return this.isSaved; } + + toJson(useNomenclatureIds = false) :any {} } diff --git a/src/billing/index.spec.ts b/src/billing/index.spec.ts index 9095182..841ecea 100644 --- a/src/billing/index.spec.ts +++ b/src/billing/index.spec.ts @@ -8,7 +8,7 @@ import { Billing } from './index'; describe('Billing', () => { - it('works', ()=> { - let billing = new Billing(); + it('config', ()=> { + Billing.config(); }); }); \ No newline at end of file diff --git a/src/billing/index.ts b/src/billing/index.ts index 70c2ed8..1e479d7 100644 --- a/src/billing/index.ts +++ b/src/billing/index.ts @@ -3,29 +3,18 @@ // This software is released under the MIT License. // http://opensource.org/licenses/mit-license.php -import { Bill } from './bill'; -import { Charge } from './charge'; -import { Modifier } from './modifier'; -import { Payment } from './payment'; +import { Bills } from './collection'; -/** - * - * - * @export - * @class Billing - */ -export class Billing { - /** - * - * - * @type {Array} - */ - bills :Array = []; - - /** - * Creates an instance of Billing. - * - */ - constructor(config: any = {}) { +import { INomenclatureAttributes } from './nomenclature/interface'; +import * as Nomenclature from './nomenclature'; + +export interface IBillingConfig { + nomenclature ?:INomenclatureAttributes; +} + +export module Billing { + export var bills :Bills = new Bills(); + export function config(config: IBillingConfig = {}) { + if (config.nomenclature) Nomenclature.init(config.nomenclature); } } diff --git a/src/billing/interface.ts b/src/billing/interface.ts new file mode 100644 index 0000000..8ae6ae0 --- /dev/null +++ b/src/billing/interface.ts @@ -0,0 +1,5 @@ +import * as NObjects from './nomenclature'; + +export interface IBillAttributes { + operatorId ?:number; +} \ No newline at end of file diff --git a/src/billing/modifier/index.ts b/src/billing/modifier/index.ts index a14719f..7bb7675 100644 --- a/src/billing/modifier/index.ts +++ b/src/billing/modifier/index.ts @@ -98,6 +98,15 @@ export class Modifier extends BillItem { if (this.bill && !~this.bill.modifiers.indexOf(this)) this.bill.modifiers.add(this); return true; } + + toJson() { + if (this.isValid) { + let json = {}; + if (this.percentRatio) json['percentRatio'] = this.percentRatio; + else json['fixedValue'] = this.fixedValue; + return json; + } + } } Modifier.validates('bill', { diff --git a/src/billing/modifier/modifier.spec.ts b/src/billing/modifier/modifier.spec.ts index 5a71aa0..db0f29a 100644 --- a/src/billing/modifier/modifier.spec.ts +++ b/src/billing/modifier/modifier.spec.ts @@ -183,4 +183,18 @@ describe('Modifier', () => { expect(modifier.isValid).toBeTruthy(); expect(bill.isValid).toBeTruthy(); }); + + describe('toJson', function() { + it('invalid', function() { + let modifier = new Modifier(); + expect(modifier.toJson()).toBeUndefined(); + }); + + it('minimal', function() { + let bill = new Bill(); + let modifier = bill.modifiers.new({ percentRatio: 0.5 }); + bill.charges.new({ price: 1}); + expect(modifier.toJson()).toEqual({ percentRatio: 0.5 }); + }); + }); }); \ No newline at end of file diff --git a/src/billing/nomenclature/department/index.ts b/src/billing/nomenclature/department/index.ts new file mode 100644 index 0000000..c79ba77 --- /dev/null +++ b/src/billing/nomenclature/department/index.ts @@ -0,0 +1,26 @@ +import { Storable } from '../../storable'; +import { IDepartment } from './interface'; +import { TaxGroup } from '../index'; + +export class Department extends Storable { + id :number; + code ?:string; + name :string; + taxGroupId: number; + + get taxRatio() :number { + if (this.taxGroup) return this.taxGroup.percentRatio; + } + + get taxGroup() :TaxGroup { + if (this.taxGroupId) return TaxGroup.find(this.taxGroupId); + } + + constructor(attributes :IDepartment) { + super(attributes); + if (attributes.id) this.id = attributes.id; + if (attributes.name) this.name = attributes.name; + if (attributes.code) this.code = attributes.code; + if (attributes.taxGroupId) this.taxGroupId = attributes.taxGroupId; + } +} \ No newline at end of file diff --git a/src/billing/nomenclature/department/interface.ts b/src/billing/nomenclature/department/interface.ts new file mode 100644 index 0000000..91301b3 --- /dev/null +++ b/src/billing/nomenclature/department/interface.ts @@ -0,0 +1,10 @@ +import { IStoreRecord, IStoreConfig } from '../../storable/interface'; + +export declare type TDepartmentConfig = Array | IStoreConfig; + +export interface IDepartment extends IStoreRecord { + id :number; + code ?:string; + name :string; + taxGroupId :number; +} \ No newline at end of file diff --git a/src/billing/nomenclature/index.ts b/src/billing/nomenclature/index.ts new file mode 100644 index 0000000..6229c28 --- /dev/null +++ b/src/billing/nomenclature/index.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2016 AlexV +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +import { PaymentType } from './paymentType'; +import { TaxGroup } from './taxGroup'; +import { Department } from './department'; +import { Plu } from './plu'; +import { Operator } from './operator'; + +import { INomenclatureAttributes } from './interface'; + +export function init(config :INomenclatureAttributes) { + if (config.plus) Plu.initStore(config.plus); + if (config.paymentTypes) PaymentType.initStore(config.paymentTypes); + if (config.taxGroups) TaxGroup.initStore(config.taxGroups); + if (config.departments) Department.initStore(config.departments); + if (config.operators) Operator.initStore(config.operators); +} + +export { + PaymentType, + TaxGroup, + Department, + Plu, + Operator +} \ No newline at end of file diff --git a/src/billing/nomenclature/interface.ts b/src/billing/nomenclature/interface.ts new file mode 100644 index 0000000..3c0579b --- /dev/null +++ b/src/billing/nomenclature/interface.ts @@ -0,0 +1,15 @@ +import { TOperatorsConfig } from './operator/interface'; +import { TTaxGroupConfig } from './taxGroup/interface'; +import { TPaymentTypeConfig } from './paymentType/interface'; +import { TDepartmentConfig } from './department/interface'; +import { TPluConfig } from './plu/interface'; + +import { IStore } from '../storable/interface'; + +export interface INomenclatureAttributes { + operators ?:TOperatorsConfig; + taxGroups ?:TTaxGroupConfig; + paymentTypes ?:TPaymentTypeConfig; + departments ?:TDepartmentConfig; + plus ?:TPluConfig; +} \ No newline at end of file diff --git a/src/billing/nomenclature/nomenclature.spec.ts b/src/billing/nomenclature/nomenclature.spec.ts new file mode 100644 index 0000000..6781091 --- /dev/null +++ b/src/billing/nomenclature/nomenclature.spec.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2016 AlexV +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +/// + +import * as Nomenclature from './index'; +import { MemoryStore } from '../store'; + +describe('Nomenclature', ()=> { + it('stores', function() { + Nomenclature.init({ + operators: [], + taxGroups: [], + paymentTypes: [{ id: 1, code: '1', name: 'Test PT', isCash: true, isFiscal: true }], + departments: [], + plus: [{ id: 1, code: '2', name: 'Test Plu', departmentId: 3, price: 4 }] + }); + let plu = Nomenclature.Plu.find(1); + expect(plu instanceof Nomenclature.Plu).toBeTruthy(); + expect([plu.id, plu.code, plu.name, plu.departmentId, plu.price]).toEqual([1, '2', 'Test Plu',3, 4]); + }); +}); \ No newline at end of file diff --git a/src/billing/nomenclature/operator/index.ts b/src/billing/nomenclature/operator/index.ts new file mode 100644 index 0000000..b1b469d --- /dev/null +++ b/src/billing/nomenclature/operator/index.ts @@ -0,0 +1,15 @@ +import { IOperator } from './interface'; +import { Storable } from '../../storable'; + +export class Operator extends Storable { + id :number; + code ?:string; + name :string = ''; + + constructor(attributes :IOperator) { + super(attributes); + if (attributes.id) this.id = attributes.id; + if (attributes.name) this.name = attributes.name; + if (attributes.code) this.code = attributes.code; + } +} \ No newline at end of file diff --git a/src/billing/nomenclature/operator/interface.ts b/src/billing/nomenclature/operator/interface.ts new file mode 100644 index 0000000..3e86e0a --- /dev/null +++ b/src/billing/nomenclature/operator/interface.ts @@ -0,0 +1,9 @@ +import { IStoreRecord, IStoreConfig } from '../../storable/interface'; + +export declare type TOperatorsConfig = Array | IStoreConfig; + +export interface IOperator extends IStoreRecord { + id :number; + code ?:string; + name :string; +} \ No newline at end of file diff --git a/src/billing/nomenclature/paymentType/index.ts b/src/billing/nomenclature/paymentType/index.ts new file mode 100644 index 0000000..190886c --- /dev/null +++ b/src/billing/nomenclature/paymentType/index.ts @@ -0,0 +1,25 @@ +// Copyright (c) 2016 AlexV +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +import { IPaymentType } from './interface'; +import { IStore } from '../../storable/interface'; +import { Storable } from '../../storable'; + +export class PaymentType extends Storable { + id :number; + code ?:string; + name :string; + isCash :boolean; + isFiscal :boolean; + + constructor(attributes :IPaymentType) { + super(attributes); + if (attributes.id) this.id = attributes.id; + if (attributes.name) this.name = attributes.name; + if (attributes.code) this.code = attributes.code; + if (attributes.isCash) this.isCash = attributes.isCash; + if (attributes.isFiscal) this.isFiscal = attributes.isFiscal; + } +} \ No newline at end of file diff --git a/src/billing/nomenclature/paymentType/interface.ts b/src/billing/nomenclature/paymentType/interface.ts new file mode 100644 index 0000000..6603e30 --- /dev/null +++ b/src/billing/nomenclature/paymentType/interface.ts @@ -0,0 +1,11 @@ +import { IStoreRecord, IStoreConfig } from '../../storable/interface'; + +export declare type TPaymentTypeConfig = Array | IStoreConfig; + +export interface IPaymentType extends IStoreRecord { + id :number; + code ?:string; + name :string; + isCash :boolean; + isFiscal :boolean; +} \ No newline at end of file diff --git a/src/billing/nomenclature/plu/index.ts b/src/billing/nomenclature/plu/index.ts new file mode 100644 index 0000000..0cb8149 --- /dev/null +++ b/src/billing/nomenclature/plu/index.ts @@ -0,0 +1,48 @@ +// Copyright (c) 2016 AlexV +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +import { IPlu } from './interface'; +import { IStore } from '../../storable/interface'; +import { Storable } from '../../storable'; +import { TaxGroup, Department } from '../index'; + +export class Plu extends Storable { + id :number; + code ?:string; + description :string; + departmentId :number; + price :number = 0; + + private _name :string; + get name() { + if (this._name) return this._name; + else if (this.department) return this.department.name; + } + set name(name :string) { + this._name = name; + } + + get taxRatio() :number { + if (this.taxGroup) return this.taxGroup.percentRatio; + } + + get taxGroup() :TaxGroup { + if (this.department) return this.department.taxGroup; + } + + get department() :Department { + if (this.departmentId) return Department.find(this.departmentId); + } + + constructor(attributes :IPlu) { + super(attributes); + if (attributes.id) this.id = attributes.id; + if (attributes.name) this.name = attributes.name; + if (attributes.description) this.description = attributes.description; + if (attributes.code) this.code = attributes.code; + if (attributes.departmentId) this.departmentId = attributes.departmentId; + if (attributes.price) this.price = attributes.price; + } +} \ No newline at end of file diff --git a/src/billing/nomenclature/plu/interface.ts b/src/billing/nomenclature/plu/interface.ts new file mode 100644 index 0000000..e26d436 --- /dev/null +++ b/src/billing/nomenclature/plu/interface.ts @@ -0,0 +1,14 @@ +import { IStoreRecord, IStoreConfig } from '../../storable/interface'; + +export declare type TPluConfig = Array | IStoreConfig; + +import { Department } from '../department'; + +export interface IPlu extends IStoreRecord { + id :number; + code ?:string; + name :string; + description ?:string; + price :number; + departmentId :number; +} \ No newline at end of file diff --git a/src/billing/nomenclature/taxGroup/index.ts b/src/billing/nomenclature/taxGroup/index.ts new file mode 100644 index 0000000..46b267b --- /dev/null +++ b/src/billing/nomenclature/taxGroup/index.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2016 AlexV +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +import { ITaxGroup } from './interface'; +import { IStore } from '../../storable/interface'; +import { Storable } from '../../storable'; + +export class TaxGroup extends Storable { + id :number; + code :string; + name :string = ''; + percentRatio :number; + + constructor(attributes :ITaxGroup) { + super(attributes); + if (attributes.id) this.id = attributes.id; + if (attributes.name) this.name = attributes.name; + if (attributes.code) this.code = attributes.code; + if (attributes.percentRatio) this.percentRatio = attributes.percentRatio; + } +} \ No newline at end of file diff --git a/src/billing/nomenclature/taxGroup/interface.ts b/src/billing/nomenclature/taxGroup/interface.ts new file mode 100644 index 0000000..327fe5c --- /dev/null +++ b/src/billing/nomenclature/taxGroup/interface.ts @@ -0,0 +1,10 @@ +import { IStoreRecord, IStoreConfig } from '../../storable/interface'; + +export declare type TTaxGroupConfig = Array | IStoreConfig; + +export interface ITaxGroup extends IStoreRecord { + id :number; + code :string; + name :string; + percentRatio :number; +} \ No newline at end of file diff --git a/src/billing/payment/index.ts b/src/billing/payment/index.ts index a98cefa..cfa16cd 100644 --- a/src/billing/payment/index.ts +++ b/src/billing/payment/index.ts @@ -6,6 +6,8 @@ import { BillItem } from '../concerns/billItem'; import { IPaymentAttributes } from './interface'; +import { PaymentType } from '../nomenclature'; + /** * * @@ -21,6 +23,32 @@ export class Payment extends BillItem { */ value :number = 0; + paymentTypeId :number; + + private _name :string; + get name() :string { + return this._name ? this._name : (this.paymentType ? this.paymentType.name : ''); + } + set name(value :string) { + this._name = value; + } + + private _isCash :boolean; + get isCash() :boolean { + return this._isCash ? this._isCash : (this.paymentType ? this.paymentType.isCash : true); + } + set isCash(value :boolean) { + this._isCash = value; + } + + private _isFiscal :boolean; + get isFiscal() :boolean { + return this._isFiscal ? this._isFiscal : (this.paymentType ? this.paymentType.isFiscal : true); + } + set isFiscal(value :boolean) { + this._isFiscal = value; + } + /** * Creates an instance of Payment. * @@ -46,10 +74,48 @@ export class Payment extends BillItem { update(attributes: IPaymentAttributes = {}) :boolean { if (attributes.bill) this._bill = attributes.bill; + if (attributes.name) this.name = attributes.name; + if (attributes.value) this.value = attributes.value; + if (attributes.paymentType) this.paymentType = attributes.paymentType; + if (attributes.paymentTypeId) this.paymentTypeId = attributes.paymentTypeId; + if (attributes.isCash) this.isCash = attributes.isCash; + if (attributes.isFiscal) this.isFiscal = attributes.isFiscal; if (attributes.value) this.value = attributes.value; if (this.bill && !~this.bill.payments.indexOf(this)) this.bill.payments.add(this); return true; } + + toJson(useNomenclatureIds = false) { + if (this.isValid) { + let json = { + name: this.name, + value: this.value, + isCash: this.isCash, + isFiscal: this.isFiscal + } + if (useNomenclatureIds) { + if (this.paymentTypeId) json['paymentTypeId'] = this.paymentTypeId; + } + return json; + } + } + + get paymentType() { + if (this.paymentTypeId) return PaymentType.find(this.paymentTypeId); + } + set paymentType(paymentType :PaymentType) { + this.paymentTypeId = paymentType.id; + } } +Payment.validates('bill', { presence: true }); +Payment.validates('value', { greaterThan: 0 }); +Payment.validates('paymentType', { + invalid: { + if: (self)=> { + return self.paymentTypeId && ! self.paymentType; + }, message: 'is not included in the list' + } +}); + export { PaymentsCollection } from './collection'; \ No newline at end of file diff --git a/src/billing/payment/interface.ts b/src/billing/payment/interface.ts index b9f0f90..69bc20f 100644 --- a/src/billing/payment/interface.ts +++ b/src/billing/payment/interface.ts @@ -4,7 +4,14 @@ // http://opensource.org/licenses/mit-license.php import { Bill } from '../bill'; +import { PaymentType } from '../nomenclature'; +/** + * + * + * @export + * @interface IPaymentAttributes + */ /** * * @@ -17,11 +24,34 @@ export interface IPaymentAttributes { * * @type {Bill} */ - bill?: Bill; + bill ?:Bill; + /** + * + * + * @type {number} + */ + value ?:number; + /** + * + * + * @type {number} + * @memberOf IPaymentAttributes + */ + name ?:string; /** * * * @type {number} + * @memberOf IPaymentAttributes + */ + paymentTypeId ?:number; + paymentType ?:PaymentType; + /** + * + * + * @type {boolean} + * @memberOf IPaymentAttributes */ - value?: number; + isCash ?:boolean; + isFiscal ?:boolean; } \ No newline at end of file diff --git a/src/billing/payment/payment.spec.ts b/src/billing/payment/payment.spec.ts index 84e036f..c154776 100644 --- a/src/billing/payment/payment.spec.ts +++ b/src/billing/payment/payment.spec.ts @@ -8,6 +8,8 @@ import { Payment } from './index'; import { Bill } from '../bill'; +import * as Nomenclature from '../nomenclature'; + describe('Payment', () => { it('default values', () => { let payment = new Payment(); @@ -50,4 +52,72 @@ describe('Payment', () => { expect(payment.delete()).toBeTruthy(); expect(bill.payments.length).toEqual(0); }); + + it('update nothing', function() { + let payment = new Payment(); + expect(()=>{ payment.update() }).not.toThrow(); + }); + + it('no paymentType defaults', function() { + let payment = new Payment(); + expect(payment.paymentType).toBeUndefined(); + expect(payment.name).toEqual(''); + expect(payment.isCash).toBeTruthy(); + expect(payment.isFiscal).toBeTruthy(); + }); + + describe('validations', function() { + it('require bill', function() { + let payment = new Payment({ value: 1 }); + expect(payment.isValid).toBeFalsy(); + expect(payment.errors.messages).toContain("Bill can't be blank"); + }); + + it('require positive value', function() { + let payment = new Payment({ value: -1 }); + expect(payment.isValid).toBeFalsy(); + expect(payment.errors.messages).toContain('Value must be greater than 0'); + }); + + it('non existing paymentTypeId', function() { + let bill = new Bill(); + let payment = bill.payments.new({ paymentTypeId: 101, value: 2 }); + expect(payment.isValid).toBeFalsy(); + expect(payment.errors.messages).toContain('PaymentType is not included in the list'); + }); + }); + + describe('nomenclature', function() { + beforeAll(function() { + Nomenclature.init({ + paymentTypes: [ + { id: 1, code: '1', name: 'Custom', isCash: false, isFiscal: false }, + { id: 2, code: '2', name: 'Custom2', isCash: true, isFiscal: false } + ] + }) + }); + + it('inherit attributes from PaymentType', function() { + let payment = new Payment({ paymentTypeId: 1 }); + expect(payment.name).toEqual('Custom'); + expect(payment.isCash).toBeFalsy(); + expect(payment.isFiscal).toBeFalsy; + }); + + it('own attributes takes precedence over relations', function() { + let payment = new Payment({ value: 1, name: 'Cash', isCash: true, isFiscal: true }); + payment.paymentTypeId = 1; + expect(payment.paymentType.isCash).toBeFalsy(); + expect(payment.name).toEqual('Cash'); + expect(payment.isCash).toBeTruthy(); + expect(payment.isFiscal).toBeTruthy(); + }); + + it('set/update nomenclature direct', function() { + let payment = new Payment(); + payment.paymentType = Nomenclature.PaymentType.find(1); + expect(payment.paymentTypeId).toEqual(1); + payment.update({ paymentType: Nomenclature.PaymentType.find(2) }); + }); + }); }); \ No newline at end of file diff --git a/src/billing/storable/index.ts b/src/billing/storable/index.ts new file mode 100644 index 0000000..b91fbfb --- /dev/null +++ b/src/billing/storable/index.ts @@ -0,0 +1,47 @@ +// Copyright (c) 2016 AlexV +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +import { IStoreRecord, IStoreConfig } from './interface'; +import { MemoryStore } from './memoryStore'; +import { IStore } from './interface'; + +export declare type ArrayOrStoreConfig = Array | IStoreConfig; + +export interface IStorableClass { + _store :IStore; + new (...a: any[]): T +} + +export abstract class Storable { + static _store :IStore; + + constructor(attributes :any = {}) { + if (attributes.store) (this.constructor).initStore(attributes.store); + } + + static find(this: IStorableClass, id: number) :T { + if (!this._store) return; + let record = this._store.get(id); + if (record) return new this(record); + } + + static all(this: IStorableClass) :Array { + if (!this._store) return; + let records = this._store.query(); + return records.map((record) => { + return new this(record); + }); + } + + static initStore(config :ArrayOrStoreConfig) { + if (config instanceof Array) { + this._store = new MemoryStore(config); + } + } +} + +export { + MemoryStore +} \ No newline at end of file diff --git a/src/billing/storable/interface.ts b/src/billing/storable/interface.ts new file mode 100644 index 0000000..2d0f28a --- /dev/null +++ b/src/billing/storable/interface.ts @@ -0,0 +1,12 @@ +export interface IStoreRecord { + id ?:number; +} + +export interface IStoreConfig { + store :string; +} + +export interface IStore { + get(id :number) :IStoreRecord; + query(filter ?:any) :Array; +} diff --git a/src/billing/storable/memoryStore.ts b/src/billing/storable/memoryStore.ts new file mode 100644 index 0000000..de4ae9c --- /dev/null +++ b/src/billing/storable/memoryStore.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2016 AlexV +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +import { IStore, IStoreRecord } from './interface'; + +export class MemoryStore implements IStore { + private _items :Array = []; + + constructor(items :Array) { + this._items = items; + } + + get(id :number) { + for (let i of this._items) { + if (i.id === id) return i; + } + } + + query(filter :any) { + return this._items; + } +} \ No newline at end of file diff --git a/src/billing/storable/store.spec.ts b/src/billing/storable/store.spec.ts new file mode 100644 index 0000000..1ec3c43 --- /dev/null +++ b/src/billing/storable/store.spec.ts @@ -0,0 +1,49 @@ +// Copyright (c) 2016 AlexV +// +// This software is released under the MIT License. +// http://opensource.org/licenses/mit-license.php + +/// + +import { Storable } from './index'; + +class TestClass extends Storable { + property :string; + constructor(a :any = {}) { + super(a); + this.property = a.property; + } +} + +describe('Storable', ()=> { + it('attach to a model', function() { + let testClass = new TestClass({ store: [{ id: 100, property: 'works' }] }); + let test = TestClass.find(100); + expect(test instanceof TestClass).toBeTruthy(); + expect(test.property).toEqual('works'); + }); + + it('find', function() { + new TestClass({ store: [ + { id: 100, property: 'One' }, + { id: 101, property: 'Two' } + ]}); + expect(TestClass.find(99)).toBeUndefined(); + let item = TestClass.find(100); + expect(item instanceof TestClass).toBeTruthy(); + expect(item.property).toEqual('One'); + item = TestClass.find(101); + expect(item.property).toEqual('Two'); + }); + + it('all', function() { + new TestClass({ store: [ + { id: 100, property: 'One' }, + { id: 101, property: 'Two' } + ]}); + let items = TestClass.all(); + expect(items.length).toEqual(2); + expect((items[0]).property).toEqual('One'); + expect((items[1]).property).toEqual('Two'); + }); +}); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index f37c6a1..4a3051f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,13 +8,15 @@ import { Bill } from './billing/bill'; import { Charge } from './billing/charge'; import { Modifier } from './billing/modifier'; import { Payment } from './billing/payment'; +import * as Nomenclature from './billing/nomenclature'; export { Billing, Bill as BillingBill, Charge as BillingCharge, Modifier as BillingModifier, - Payment as BillingPayment + Payment as BillingPayment, + Nomenclature as BillingNomenclature } export default Billing; \ No newline at end of file