From c16b47b132639223d50448ca8556a0da1661e38f Mon Sep 17 00:00:00 2001 From: AlexVangelov Date: Sat, 29 Oct 2016 14:20:15 -0400 Subject: [PATCH 1/3] fix transfer modifier between charges of same bill --- src/billing/charge/charge.spec.ts | 20 ++++++++++++++++++++ src/billing/charge/index.ts | 5 ++++- src/billing/concerns/billItem.ts | 2 +- src/billing/modifier/index.ts | 5 ++++- src/billing/modifier/modifier.spec.ts | 13 +++++++++++++ src/billing/payment/index.ts | 2 +- 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/billing/charge/charge.spec.ts b/src/billing/charge/charge.spec.ts index ec247f0..0a82597 100644 --- a/src/billing/charge/charge.spec.ts +++ b/src/billing/charge/charge.spec.ts @@ -250,6 +250,26 @@ describe('Charge', () => { expect(modifier.charge).toEqual(charge); }); + it('delete modifier', ()=> { + let charge = new Charge({ price: 1, modifier: { fixedValue: 1 }}); + expect(charge.modifier).toBeDefined(); + charge.deleteModifier(); + expect(charge.modifier).toBeUndefined(); + }); + + it('transfer modifier', ()=> { + let modifier = new Modifier({ fixedValue: 1 }); + let charge1 = new Charge({ price: 1, modifier: modifier }); + let charge2 = new Charge({ price: 2 }); + expect(charge1.modifier).toEqual(modifier); + expect(modifier.charge).toEqual(charge1); + charge2.update({ modifier: modifier }); + expect(modifier.charge).toEqual(charge2); + expect(charge2.modifier).toEqual(modifier); + expect(charge1).not.toEqual(charge2); + expect(charge1.modifier).toBeUndefined(); + }); + describe('validations', function() { it('require bill', function() { let charge = new Charge({ price: 1 }); diff --git a/src/billing/charge/index.ts b/src/billing/charge/index.ts index 64e9c09..9ad16c1 100644 --- a/src/billing/charge/index.ts +++ b/src/billing/charge/index.ts @@ -95,7 +95,10 @@ export class Charge extends BillItem { 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; + if (modifier.charge !== this) { + if (modifier.charge) modifier.charge.deleteModifier(); + modifier.charge = this; + } this._modifier = modifier; if (this.bill && !~this.bill.charges.indexOf(this)) this.bill.charges.add(this); } diff --git a/src/billing/concerns/billItem.ts b/src/billing/concerns/billItem.ts index 54f24c5..abf0839 100644 --- a/src/billing/concerns/billItem.ts +++ b/src/billing/concerns/billItem.ts @@ -18,7 +18,7 @@ export declare type BillItemAttributes = IChargeAttributes | IModifierAttributes * @class BillItem */ export abstract class BillItem extends ValidationModel { - + state :any; /** * * diff --git a/src/billing/modifier/index.ts b/src/billing/modifier/index.ts index 84ee610..8a40ed8 100644 --- a/src/billing/modifier/index.ts +++ b/src/billing/modifier/index.ts @@ -85,7 +85,10 @@ export class Modifier extends BillItem { if (attributes.bill) this._bill = attributes.bill; if (attributes.percentRatio) this.percentRatio = attributes.percentRatio; if (attributes.fixedValue) this.fixedValue = attributes.fixedValue; - if (attributes.charge) this._charge = attributes.charge; + if (attributes.charge) { + if (this.charge) this.charge.deleteModifier(); + this._charge = attributes.charge; + } if (this.charge) { let chargeBill = this.charge.bill; if (chargeBill) { diff --git a/src/billing/modifier/modifier.spec.ts b/src/billing/modifier/modifier.spec.ts index db0f29a..4eb3dbc 100644 --- a/src/billing/modifier/modifier.spec.ts +++ b/src/billing/modifier/modifier.spec.ts @@ -175,6 +175,19 @@ describe('Modifier', () => { expect(()=>{ modifier.update() }).not.toThrow(); }); + it('update charge', ()=> { + let modifier = new Modifier({ fixedValue: 1 }); + let charge1 = new Charge({ price: 1, modifier: modifier }); + let charge2 = new Charge({ price: 2 }); + expect(charge1.modifier).toEqual(modifier); + expect(modifier.charge).toEqual(charge1); + modifier.update({ charge: charge2 }); + expect(modifier.charge).toEqual(charge2); + expect(charge2.modifier).toEqual(modifier); + expect(charge1).not.toEqual(charge2); + expect(charge1.modifier).toBeUndefined(); + }); + it('100% global', function() { let bill = new Bill(); let modifier = bill.modifiers.new({ percentRatio: -1.0 }); diff --git a/src/billing/payment/index.ts b/src/billing/payment/index.ts index c7a63b2..329a4de 100644 --- a/src/billing/payment/index.ts +++ b/src/billing/payment/index.ts @@ -120,7 +120,7 @@ Payment.validates('value', { greaterThan: 0 }); Payment.validates('paymentType', { invalid: { if: (self)=> { - return self.paymentTypeId && ! self.paymentType; + return self.paymentTypeId && !self.paymentType; }, message: 'is not included in the list' } }); From b22bd7850a74c0afaf60b27e2c588f96129ccf67 Mon Sep 17 00:00:00 2001 From: AlexVangelov Date: Sat, 29 Oct 2016 23:24:59 -0400 Subject: [PATCH 2/3] sample angular2, UI friendly changes and fixes --- README.md | 4 + package.json | 8 +- samples/browser/angular2/README.md | 9 ++ samples/browser/angular2/config.js | 46 ++++++++ samples/browser/angular2/index.html | 29 +++++ samples/browser/angular2/src/main.ts | 5 + .../browser/angular2/src/widget/billing.html | 95 +++++++++++++++++ .../browser/angular2/src/widget/billing.ts | 100 ++++++++++++++++++ samples/browser/angular2/src/widget/charge.ts | 74 +++++++++++++ .../angular2/src/widget/chargeForm.html | 60 +++++++++++ .../browser/angular2/src/widget/modifier.ts | 66 ++++++++++++ .../angular2/src/widget/modifierForm.html | 42 ++++++++ .../browser/angular2/src/widget/payment.ts | 46 ++++++++ .../angular2/src/widget/paymentForm.html | 35 ++++++ samples/browser/angular2/style.css | 7 ++ src/billing/charge/charge.spec.ts | 12 +++ src/billing/charge/index.ts | 9 +- src/billing/modifier/index.ts | 17 +-- src/billing/modifier/modifier.spec.ts | 22 ++++ src/billing/payment/index.ts | 15 ++- src/billing/payment/payment.spec.ts | 9 ++ 21 files changed, 687 insertions(+), 23 deletions(-) create mode 100644 samples/browser/angular2/README.md create mode 100644 samples/browser/angular2/config.js create mode 100644 samples/browser/angular2/index.html create mode 100644 samples/browser/angular2/src/main.ts create mode 100644 samples/browser/angular2/src/widget/billing.html create mode 100644 samples/browser/angular2/src/widget/billing.ts create mode 100644 samples/browser/angular2/src/widget/charge.ts create mode 100644 samples/browser/angular2/src/widget/chargeForm.html create mode 100644 samples/browser/angular2/src/widget/modifier.ts create mode 100644 samples/browser/angular2/src/widget/modifierForm.html create mode 100644 samples/browser/angular2/src/widget/payment.ts create mode 100644 samples/browser/angular2/src/widget/paymentForm.html create mode 100644 samples/browser/angular2/style.css diff --git a/README.md b/README.md index 6f5fde1..6515dd3 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,10 @@ bill.toJson() //A JSON object (without circular references) boolean .isValid // check validity .errors // errors format [{ propertyName: { validationName: 'Human readable error' }}] +### Other + + .state // Unused instance variable available for UI purposes + ## Using the nomenclature ([samples/node/usingNomenclature.js](samples/node/usingNomenclature.js)) ```javascript var billing = require('../../').Billing; diff --git a/package.json b/package.json index 7a10185..c744322 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "billing", - "version": "1.0.0", + "version": "1.0.1", "description": "Billing Module Js", "author": "AlexV", "repository": { @@ -20,7 +20,8 @@ "typings": "typings install", "test": "karma start karma.conf.js", "build": "tsc -d", - "dist": "webpack --progress --profile --bail" + "dist": "webpack --progress --profile --bail", + "sample-angular": "webpack-dev-server --content-base samples/browser/angular2/" }, "devDependencies": { "awesome-typescript-loader": "^2.2.4", @@ -35,7 +36,8 @@ "source-map": "^0.5.6", "typescript": "^2.0.0", "typings": "^1.3.3", - "webpack": "^1.13.2" + "webpack": "^1.13.2", + "webpack-dev-server": "^1.16.2" }, "typings": "./lib/index.d.ts", "files": [ diff --git a/samples/browser/angular2/README.md b/samples/browser/angular2/README.md new file mode 100644 index 0000000..75f44cb --- /dev/null +++ b/samples/browser/angular2/README.md @@ -0,0 +1,9 @@ +### BillingJs Demo (Angular2) + +Demonstrating BillingJs + +GitHub: [https://github.com/AlexVangelov/billing-js](https://github.com/AlexVangelov/billing-js) + +[![npm version](https://badge.fury.io/js/billing.svg)](https://badge.fury.io/js/billing) +[![Build status](https://travis-ci.org/AlexVangelov/billing-js.svg)](https://travis-ci.org/AlexVangelov/billing-js) + diff --git a/samples/browser/angular2/config.js b/samples/browser/angular2/config.js new file mode 100644 index 0000000..a8e7c20 --- /dev/null +++ b/samples/browser/angular2/config.js @@ -0,0 +1,46 @@ +System.config({ + //use typescript for compilation + transpiler: 'typescript', + //typescript compiler options + typescriptOptions: { + emitDecoratorMetadata: true + }, + paths: { + 'npm:': 'https://unpkg.com/' + }, + //map tells the System loader where to look for things + map: { + + 'app': './src', + + '@angular/core': 'npm:@angular/core@2.0.2/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common@2.0.2/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler@2.0.2/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser@2.0.2/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@2.0.2/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http@2.0.2/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router@2.0.2/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms@2.0.2/bundles/forms.umd.js', + + '@angular/core/testing': 'npm:@angular/core@2.0.2/bundles/core-testing.umd.js', + '@angular/common/testing': 'npm:@angular/common@2.0.2/bundles/common-testing.umd.js', + '@angular/compiler/testing': 'npm:@angular/compiler@2.0.2/bundles/compiler-testing.umd.js', + '@angular/platform-browser/testing': 'npm:@angular/platform-browser@2.0.2/bundles/platform-browser-testing.umd.js', + '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic@2.0.2/bundles/platform-browser-dynamic-testing.umd.js', + '@angular/http/testing': 'npm:@angular/http@2.0.2/bundles/http-testing.umd.js', + '@angular/router/testing': 'npm:@angular/router@2.0.2/bundles/router-testing.umd.js', + + 'rxjs': 'npm:rxjs', + 'typescript': 'npm:typescript@2.0.2/lib/typescript.js' + }, + //packages defines our app package + packages: { + app: { + main: './main.ts', + defaultExtension: 'ts' + }, + rxjs: { + defaultExtension: 'js' + } + } +}); \ No newline at end of file diff --git a/samples/browser/angular2/index.html b/samples/browser/angular2/index.html new file mode 100644 index 0000000..f764939 --- /dev/null +++ b/samples/browser/angular2/index.html @@ -0,0 +1,29 @@ + + + + + + BillingJs Playground + + + + + + + + + + + + + + + +
+ Loading... +
+ + diff --git a/samples/browser/angular2/src/main.ts b/samples/browser/angular2/src/main.ts new file mode 100644 index 0000000..7d96fa8 --- /dev/null +++ b/samples/browser/angular2/src/main.ts @@ -0,0 +1,5 @@ +//main entry point +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { WidgetBillingModule } from './widget/billing'; + +platformBrowserDynamic().bootstrapModule(WidgetBillingModule) \ No newline at end of file diff --git a/samples/browser/angular2/src/widget/billing.html b/samples/browser/angular2/src/widget/billing.html new file mode 100644 index 0000000..ce96397 --- /dev/null +++ b/samples/browser/angular2/src/widget/billing.html @@ -0,0 +1,95 @@ +

+ + BillingJs Sample (Angular2) +

npm: https://www.npmjs.com/package/billing

+

+
{{ bill.errors.messages.join(', ') }}
+
+
+ +

Charges

+
+ +
+
+ + +
+
+
+
+ +
+
+ +
+
+
+ +

Payments

+
+
+
+ +
+
+ +
+
+ + + + + View on GitHub + +
+ + \ No newline at end of file diff --git a/samples/browser/angular2/src/widget/billing.ts b/samples/browser/angular2/src/widget/billing.ts new file mode 100644 index 0000000..5b2725e --- /dev/null +++ b/samples/browser/angular2/src/widget/billing.ts @@ -0,0 +1,100 @@ +import { Component, NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { WidgetCharge } from './charge'; +import { WidgetModifier } from './modifier'; +import { WidgetPayment } from './payment'; + +declare var billingJs: any; + +@Component({ + selector: 'widget-billing', + templateUrl: 'src/widget/billing.html' +}) +export class WidgetBilling { + selectedItem :any; + selectedCollection :any; + bill :any; + dialogType :any; + + constructor() { + this.bill = billingJs.Billing.bills.new(); + + this.bill = billingJs.Billing.bills.new(); + this.bill.charges.new({ price: 8.3, name: 'Umbrella', description: 'Unmatched quality and classic design' }); + this.bill.charges.new({ price: 135, name: 'Briefcase', description: 'High-quality ballistic nylon fabric', + modifier: { percentRatio: -0.1 } }); + this.bill.charges.new({ qty: 3, price: .65, description: 'Accessories' }); + this.bill.payments.new({ name: 'Cash' }); + } + + private buildCollectionItem() :any { + let item = this.selectedCollection.new(); + item.state = true; //state is for UI purposes (not used in billing) + return item; + } + + editCharge(charge ?:any) { + this.selectedCollection = this.bill.charges; + this.selectedItem = charge || this.buildCollectionItem(); + this.dialogType = 'charge'; + } + editModifier(modifier ?:any) { + this.selectedCollection = this.bill.modifiers; + this.selectedItem = modifier || this.buildCollectionItem(); + this.dialogType = 'modifier'; + } + editPayment(payment ?:any) { + this.selectedCollection = this.bill.payments; + this.selectedItem = payment || this.buildCollectionItem(); + this.dialogType = 'payment'; + } + + done(modified :boolean) { + if (!modified && this.selectedItem.state) this.selectedItem.delete(); + delete this.selectedItem.state; + delete this.selectedItem; + delete this.dialogType; + if (modified) this.bill.isValid; + } + + isSelected(item) { + return item === this.selectedItem; + } + + labelForModifier(modifier :any) { + let r = modifier.percentRatio; + let s = r > 0 ? '+' : ''; + let label = r ? s + (r * 100) + '%' : ''; + return `${label} ${modifier.value > 0 ? 'Surcharge' : 'Discount'}`; + } + + showJson() { + this.selectedItem = this.bill; + this.dialogType = 'json'; + } + + reset() { + this.bill = billingJs.Billing.bills.new(); + } + + get globalModifiers() { + return this.bill.modifiers.filter( mod => { + return !mod.charge; + }); + } +} + +@NgModule({ + imports: [ BrowserModule, ReactiveFormsModule ], + declarations: [ + WidgetBilling, + WidgetCharge, + WidgetModifier, + WidgetPayment + ], + bootstrap: [ WidgetBilling ], + schemas: [ CUSTOM_ELEMENTS_SCHEMA ] +}) +export class WidgetBillingModule {} \ No newline at end of file diff --git a/samples/browser/angular2/src/widget/charge.ts b/samples/browser/angular2/src/widget/charge.ts new file mode 100644 index 0000000..086c102 --- /dev/null +++ b/samples/browser/angular2/src/widget/charge.ts @@ -0,0 +1,74 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; + +@Component({ + selector: 'widget-charge', + templateUrl: 'src/widget/chargeForm.html' +}) +export class WidgetCharge { + @Input() charge: any; + @Output() done = new EventEmitter(); + + origCharge :any; + chargeForm :FormGroup; + origModifier :any; + modifierForm :FormGroup; + errorMsg :string; + + constructor(private fb: FormBuilder) {}; + + ngOnInit() { + this.origCharge = { + qty: this.charge.qty, + price: this.charge.price || null, + name: this.charge.name, + description: this.charge.description + }; + this.chargeForm = this.fb.group(this.origCharge); + + if (this.charge.modifier) { + this.origModifier = { + modifierKind: 'fixed', + fixedValue: this.charge.modifier.fixedValue, + percent: null + }; + if (this.charge.modifier.percentRatio) { + this.origModifier.percent = this.charge.modifier.percentRatio * 100; + this.origModifier.modifierKind = 'percent' + } + this.modifierForm = this.fb.group(this.origModifier); + } + } + + destroy() { + this.charge.delete(); + this.done.emit(true); + } + + onSubmit(ev :any) { + ev.preventDefault(); + this.charge.update(this.chargeForm.value); + if (this.charge.isValid) this.done.emit(true); + else { + this.errorMsg = this.charge.errors.messages.join(', '); + this.charge.update(this.origCharge); //TODO provide rollback mechanism + } + return true; + } + + addModifier() { + this.modifierForm = this.fb.group({ + modifierKind: 'percent', + fixedValue: null, + percent: null + }); + } + + removeModifier() { + delete this.modifierForm; + } + + cancel() { + this.done.emit(false); + } +} \ No newline at end of file diff --git a/samples/browser/angular2/src/widget/chargeForm.html b/samples/browser/angular2/src/widget/chargeForm.html new file mode 100644 index 0000000..2187cb9 --- /dev/null +++ b/samples/browser/angular2/src/widget/chargeForm.html @@ -0,0 +1,60 @@ +
+ +
\ No newline at end of file diff --git a/samples/browser/angular2/src/widget/modifier.ts b/samples/browser/angular2/src/widget/modifier.ts new file mode 100644 index 0000000..5c70240 --- /dev/null +++ b/samples/browser/angular2/src/widget/modifier.ts @@ -0,0 +1,66 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; + +@Component({ + selector: 'widget-modifier', + templateUrl: 'src/widget/modifierForm.html' +}) +export class WidgetModifier { + @Input() modifier: any; + @Input() charges: Array; + @Output() done = new EventEmitter(); + + origModifier :any; + modifierForm :FormGroup; + errorMsg :string; + + constructor(private fb: FormBuilder) {}; + + ngOnInit() { + this.origModifier = { + modifierKind: 'fixed', + percent: null, + fixedValue: this.modifier.fixedValue || null, + chargeIndex: this.charges.indexOf(this.modifier.charge) + }; + if (this.modifier.percentRatio) { + this.origModifier.percent = this.modifier.percentRatio * 100; + this.origModifier.modifierKind = 'percent'; + } + this.modifierForm = this.fb.group(this.origModifier); + } + + destroy() { + this.modifier.delete(); + this.done.emit(true); + } + + onSubmit(ev :any) { + let data = { + charge: this.charges[this.modifierForm.value.chargeIndex] || null, + percentRatio: this.modifierForm.value.percent ? this.modifierForm.value.percent / 100 : null, + fixedValue: this.modifierForm.value.fixedValue + }; + ev.preventDefault(); + if (this.modifierForm.value.modifierKind === 'fixed') data.percentRatio = null; + else data.fixedValue = null; + this.modifier.update(data); + if (this.modifier.isValid) this.done.emit(true); + else { + let rollback = { + fixedValue: this.origModifier.fixedValue, + percentRatio: null, + charge: null + }; + if (this.origModifier.percent) rollback.percentRatio = this.origModifier.percent / 100; + if (this.origModifier.chargeIndex > -1) rollback.charge = this.charges[this.origModifier.chargeIndex]; + this.errorMsg = this.modifier.errors.messages.join(', '); + this.modifier.update(rollback); + } + return true; + } + + cancel() { + this.done.emit(false); + } +} \ No newline at end of file diff --git a/samples/browser/angular2/src/widget/modifierForm.html b/samples/browser/angular2/src/widget/modifierForm.html new file mode 100644 index 0000000..29f27ff --- /dev/null +++ b/samples/browser/angular2/src/widget/modifierForm.html @@ -0,0 +1,42 @@ +
+ +
\ No newline at end of file diff --git a/samples/browser/angular2/src/widget/payment.ts b/samples/browser/angular2/src/widget/payment.ts new file mode 100644 index 0000000..85b6985 --- /dev/null +++ b/samples/browser/angular2/src/widget/payment.ts @@ -0,0 +1,46 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; + +@Component({ + selector: 'widget-payment', + templateUrl: 'src/widget/paymentForm.html' +}) +export class WidgetPayment { + @Input() payment: any; + @Output() done = new EventEmitter(); + + origPayment :any; + paymentForm :FormGroup; + errorMsg :string; + + constructor(private fb: FormBuilder) {}; + + ngOnInit() { + this.origPayment = { + name: this.payment.name, + value: this.payment.value || null, + isCash: this.payment.isCash + }; + this.paymentForm = this.fb.group(this.origPayment); + } + + destroy() { + this.payment.delete(); + this.done.emit(true); + } + + onSubmit(ev :any) { + ev.preventDefault(); + this.payment.update(this.paymentForm.value); + if (this.payment.isValid) this.done.emit(true); + else { + this.errorMsg = this.payment.errors.messages.join(', '); + this.payment.update(this.origPayment); + } + return true; + } + + cancel() { + this.done.emit(false); + } +} \ No newline at end of file diff --git a/samples/browser/angular2/src/widget/paymentForm.html b/samples/browser/angular2/src/widget/paymentForm.html new file mode 100644 index 0000000..0b4c64b --- /dev/null +++ b/samples/browser/angular2/src/widget/paymentForm.html @@ -0,0 +1,35 @@ +
+ +
\ No newline at end of file diff --git a/samples/browser/angular2/style.css b/samples/browser/angular2/style.css new file mode 100644 index 0000000..371bbf9 --- /dev/null +++ b/samples/browser/angular2/style.css @@ -0,0 +1,7 @@ +widget-billing { + font-size: 12px; +} +widget-billing .global-modifier:not(:hover) { + background-color: #f9f9f9; +} + diff --git a/src/billing/charge/charge.spec.ts b/src/billing/charge/charge.spec.ts index 0a82597..f29b676 100644 --- a/src/billing/charge/charge.spec.ts +++ b/src/billing/charge/charge.spec.ts @@ -243,6 +243,18 @@ describe('Charge', () => { expect(charge.update()).toBeTruthy(); }); + it('update zero or empty values', ()=> { + let charge = new Charge({ qty: 2, price: 2, name: 'n', description: 'd' }); + charge.update({ qty: 0 }); + expect(charge.qty).toEqual(0); + charge.update({ price: 0 }); + expect(charge.price).toEqual(0); + charge.update({ name: '' }); + expect(charge.name).toEqual(''); + charge.update({ description: '' }); + expect(charge.description).toEqual(''); + }); + it('set modifier should adopt charge', function() { let charge = new Charge({ price: 1 }); let modifier = new Modifier({ fixedValue: 1 }); diff --git a/src/billing/charge/index.ts b/src/billing/charge/index.ts index 9ad16c1..03898d5 100644 --- a/src/billing/charge/index.ts +++ b/src/billing/charge/index.ts @@ -150,10 +150,10 @@ 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.qty) this.qty = attributes.qty; + if (typeof attributes.name !== 'undefined') this.name = attributes.name; + if (typeof attributes.description !== 'undefined') this._description = attributes.description; + if (typeof attributes.price !== 'undefined') this._price = attributes.price; + if (typeof attributes.qty !== 'undefined') 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; @@ -239,6 +239,7 @@ export class Charge extends BillItem { Charge.validates('bill', { presence: true }); Charge.validates('price', { greaterThan: 0 }); +Charge.validates('qty', { greaterThan: 0 }); Charge.validates('finalValue', { greaterThanOrEqualTo: 0 }); Charge.validates('modifier', { invalid: (self)=> { return self.modifier && !self.modifier.isValid; diff --git a/src/billing/modifier/index.ts b/src/billing/modifier/index.ts index 8a40ed8..511560c 100644 --- a/src/billing/modifier/index.ts +++ b/src/billing/modifier/index.ts @@ -50,8 +50,11 @@ export class Modifier extends BillItem { } set charge(charge :Charge) { - if (charge instanceof Charge) this._charge = charge; - else throw new ReferenceError('Set Charge by attributes is not allowed for modifier'); + if (charge instanceof Charge || charge === null) { + if (this.charge) this.charge.deleteModifier(); + if (charge) this._charge = charge; + else delete this._charge; + } else throw new ReferenceError('Set Charge by attributes is not allowed for modifier'); } /** @@ -83,12 +86,10 @@ export class Modifier extends BillItem { update(attributes: IModifierAttributes = {}) :boolean { if (attributes.bill) this._bill = attributes.bill; - if (attributes.percentRatio) this.percentRatio = attributes.percentRatio; - if (attributes.fixedValue) this.fixedValue = attributes.fixedValue; - if (attributes.charge) { - if (this.charge) this.charge.deleteModifier(); - this._charge = attributes.charge; - } + if (attributes.percentRatio === null) delete this.percentRatio; + else if (attributes.percentRatio) this.percentRatio = attributes.percentRatio; + if (typeof attributes.fixedValue!== 'undefined') this.fixedValue = attributes.fixedValue || 0; + if (typeof attributes.charge !== 'undefined') this.charge = attributes.charge; if (this.charge) { let chargeBill = this.charge.bill; if (chargeBill) { diff --git a/src/billing/modifier/modifier.spec.ts b/src/billing/modifier/modifier.spec.ts index 4eb3dbc..c5026d3 100644 --- a/src/billing/modifier/modifier.spec.ts +++ b/src/billing/modifier/modifier.spec.ts @@ -175,6 +175,20 @@ describe('Modifier', () => { expect(()=>{ modifier.update() }).not.toThrow(); }); + it('update percent null', ()=> { + let modifier = new Modifier({ percentRatio: 1 }); + expect(modifier.percentRatio).toEqual(1); + modifier.update({ percentRatio: null }); + expect(modifier.percentRatio).toBeUndefined(); + }); + + it('update fixed null', ()=> { + let modifier = new Modifier({ fixedValue: 1 }); + expect(modifier.fixedValue).toEqual(1); + modifier.update({ fixedValue: null }); + expect(modifier.fixedValue).toEqual(0); + }); + it('update charge', ()=> { let modifier = new Modifier({ fixedValue: 1 }); let charge1 = new Charge({ price: 1, modifier: modifier }); @@ -188,6 +202,14 @@ describe('Modifier', () => { expect(charge1.modifier).toBeUndefined(); }); + it('update from charge to global', ()=> { + let modifier = new Modifier({ fixedValue: 1 }); + let charge = new Charge({ price: 1, modifier: modifier }); + expect(modifier.charge).toEqual(charge); + modifier.update({ charge: null }); + expect(modifier.charge).toBeUndefined(); + }); + it('100% global', function() { let bill = new Bill(); let modifier = bill.modifiers.new({ percentRatio: -1.0 }); diff --git a/src/billing/payment/index.ts b/src/billing/payment/index.ts index 329a4de..8a92a38 100644 --- a/src/billing/payment/index.ts +++ b/src/billing/payment/index.ts @@ -36,7 +36,7 @@ export class Payment extends BillItem { private _isCash :boolean; get isCash() :boolean { - return this._isCash ? this._isCash : (this.paymentType ? this.paymentType.isCash : true); + return (typeof this._isCash !== 'undefined') ? this._isCash : (this.paymentType ? this.paymentType.isCash : true); } set isCash(value :boolean) { this._isCash = value; @@ -44,7 +44,7 @@ export class Payment extends BillItem { private _isFiscal :boolean; get isFiscal() :boolean { - return this._isFiscal ? this._isFiscal : (this.paymentType ? this.paymentType.isFiscal : true); + return (typeof this._isFiscal !== 'undefined') ? this._isFiscal : (this.paymentType ? this.paymentType.isFiscal : true); } set isFiscal(value :boolean) { this._isFiscal = value; @@ -58,7 +58,7 @@ export class Payment extends BillItem { constructor(attributes: IPaymentAttributes = {}) { super(attributes.bill); if (!attributes.value) { - if (this.bill) this.value = this.bill.total - this.bill.payments.sum(); + if (this.bill) this.value = Math.round((this.bill.total - this.bill.payments.sum()) * 100) / 100; } this.update(attributes); } @@ -75,13 +75,12 @@ 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 (typeof attributes.name !== 'undefined') this.name = attributes.name; + if (typeof attributes.value !== 'undefined') 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 (typeof attributes.isCash !== 'undefined') this._isCash = attributes.isCash; + if (typeof attributes.isFiscal !== 'undefined') this._isFiscal = attributes.isFiscal; if (this.bill && !~this.bill.payments.indexOf(this)) this.bill.payments.add(this); return true; } diff --git a/src/billing/payment/payment.spec.ts b/src/billing/payment/payment.spec.ts index 9ddff40..f716c97 100644 --- a/src/billing/payment/payment.spec.ts +++ b/src/billing/payment/payment.spec.ts @@ -57,6 +57,15 @@ describe('Payment', () => { let payment = new Payment(); expect(()=>{ payment.update() }).not.toThrow(); }); + + it('update flags', ()=> { + let payment = new Payment(); + expect(payment.isCash).toBeTruthy(); + expect(payment.isFiscal).toBeTruthy(); + payment.update({ isCash: false, isFiscal: false }); + expect(payment.isCash).toBeFalsy(); + expect(payment.isFiscal).toBeFalsy(); + }); it('no paymentType defaults', function() { let payment = new Payment(); From 624b81286088cbed8d678abadefc193c862aedd5 Mon Sep 17 00:00:00 2001 From: AlexVangelov Date: Sun, 30 Oct 2016 00:01:35 -0400 Subject: [PATCH 3/3] update readme --- CHANGELOG.md | 5 +++++ README.md | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2dc447..a9db54f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.0.1 + + - UI friendly changes and fixes. + - Add Angular2 usage sample. + ## 1.0.0 - Fix UMD lib. Add browser usage sample (breaking changes will go to 2.x) diff --git a/README.md b/README.md index 6515dd3..97046ed 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ Billing Module JS npm install billing + * Implements base billing standart. + * Data binding frameworks friendly. + * Example: [BillingJs Demo (Angular2)](https://plnkr.co/n0srTG) + ## Usage ### Right constructor ([samples/node/rightConstructor.js](samples/node/rightConstructor.js))