diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000..449691b7
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+save-exact=true
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index df4865d2..071bf4c1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,6 @@
language: node_js
node_js:
+<<<<<<< HEAD
- "4.2.6"
before_script:
@@ -9,3 +10,12 @@ script:
- npm run test
- npm run test-e2e
+=======
+ - "5.6.0"
+
+before_install:
+ - npm install -g bower gulp
+
+script:
+ - npm run test
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
diff --git a/README.md b/README.md
index 6f326e6d..cff5d7d4 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,15 @@
A seed project for custom Four51 Solutions built on AngularJS
***
+<<<<<<< HEAD
+=======
+## Build Status
+### development
+[![Build Status](https://travis-ci.org/ordercloud-api/angular-buyer.svg?branch=development)](https://travis-ci.org/ordercloud-api/angular-buyer)
+### master
+[![Build Status](https://travis-ci.org/ordercloud-api/angular-buyer.svg?branch=master)](https://travis-ci.org/ordercloud-api/angular-buyer)
+
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
## Get started
Node.js is required for the following node package manager (npm) tasks. If you don't have node.js installed, you can download it [here](http://nodejs.org/).
@@ -144,4 +153,8 @@ task, which runs `build` and then `compile`:
```sh
$ gulp
-```
\ No newline at end of file
+<<<<<<< HEAD
+```
+=======
+```
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
diff --git a/bower.json b/bower.json
index b4fd13fd..e348e816 100644
--- a/bower.json
+++ b/bower.json
@@ -24,6 +24,7 @@
],
"dependencies": {
"bootstrap": "^3.3.6",
+<<<<<<< HEAD
"angular": "^1.5.7",
"angular-animate": "^1.5.7",
"angular-sanitize": "^1.5.7",
@@ -47,5 +48,34 @@
"resolutions": {
"angular": "^1.5.7",
"angular-animate": "^1.5.7"
+=======
+ "angular": "^1.6.0",
+ "angular-resource": "^1.6.0",
+ "angular-animate": "^1.6.0",
+ "angular-sanitize": "^1.6.0",
+ "angular-touch": "^1.6.0",
+ "angular-messages": "^1.6.0",
+ "angular-toastr": "^1.7.0",
+ "angular-ui-tree": "^2.16.0",
+ "angular-bootstrap": "^1.3.3",
+ "ordercloud-ng-sdk": "1.0.27",
+ "angular-auto-validate": "^1.19.5",
+ "font-awesome": "^4.6.3",
+ "angular-localforage": "^1.2.5",
+ "underscore": "^1.8.3",
+ "angular-ui-router": "^0.3.1",
+ "angular-tree-control": "^0.2.28",
+ "angular-sticky": "angular-sticky-plugin#^0.3.0",
+ "angular-busy2": "^5.2.0"
+ },
+ "devDependencies": {
+ "angular-mocks": "^1.6.0"
+ },
+ "resolutions": {
+ "angular": "^1.6.0",
+ "angular-animate": "^1.6.0",
+ "angular-resource": "^1.6.0",
+ "angular-cookies": "^1.6.0"
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
}
}
diff --git a/gulp.config.js b/gulp.config.js
index c07c8ed3..78cb0584 100644
--- a/gulp.config.js
+++ b/gulp.config.js
@@ -1,6 +1,9 @@
var source = './src/',
assets = 'assets/',
+<<<<<<< HEAD
components = './../Components/',
+=======
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
build = './build/',
bowerFiles = './bower_components/',
npmFiles = './node_modules',
@@ -36,6 +39,7 @@ module.exports = {
'!' + source + '**/*.spec.js',
'!' + source + '**/*.test.js'
],
+<<<<<<< HEAD
components: {
dir: components,
scripts: [
@@ -51,6 +55,13 @@ module.exports = {
},
appFiles: [
build + '**/app.js',
+=======
+ appFiles: [
+ build + '**/app.module.js',
+ build + '**/app.config.js',
+ build + '**/app.run.js',
+ build + '**/app.controller.js',
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
build + '**/*.js',
build + '**/*.css',
source + '**/*.css'
@@ -79,7 +90,11 @@ module.exports = {
function getConstants() {
var result = {};
+<<<<<<< HEAD
var constants = JSON.parse(fs.readFileSync(source + 'app/app.config.json'));
+=======
+ var constants = JSON.parse(fs.readFileSync(source + 'app/app.constants.json'));
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
var environment = process.env.environment || constants.environment;
switch (environment) {
case 'local':
diff --git a/gulp/build/app-config.js b/gulp/build/app-config.js
index 68160ff7..d9b7ca59 100644
--- a/gulp/build/app-config.js
+++ b/gulp/build/app-config.js
@@ -4,12 +4,20 @@ var gulp = require('gulp'),
ngConstant = require('gulp-ng-constant');
gulp.task('clean:app-config', function() {
+<<<<<<< HEAD
return del(config.build + '**/app.config.js');
+=======
+ return del(config.build + '**/app.constants.js');
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
});
gulp.task('app-config', ['clean:app-config'], function() {
return gulp
+<<<<<<< HEAD
.src(config.src + '**/app.config.json')
+=======
+ .src(config.src + '**/app.constants.json')
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
.pipe(ngConstant(config.ngConstantSettings))
.pipe(gulp.dest(config.build));
});
diff --git a/gulp/build/inject.js b/gulp/build/inject.js
index 888fcf5f..63e77c71 100644
--- a/gulp/build/inject.js
+++ b/gulp/build/inject.js
@@ -11,10 +11,18 @@ gulp.task('clean:inject', function() {
gulp.task('inject', ['clean:inject', 'scripts', 'assets', 'app-config', 'bower-fonts', 'styles'], function() {
var target = gulp.src(config.index),
bowerFiles = gulp.src(mainBowerFiles({filter: ['**/*.js', '**/*.css']}), {read: false}),
+<<<<<<< HEAD
appFiles = gulp.src([].concat(config.appFiles, config.components.styles.css), {read: false});
return target
.pipe(inject(bowerFiles, {name: 'bower'}))
.pipe(inject(appFiles))
+=======
+ appFiles = gulp.src([].concat(config.appFiles), {read: false});
+
+ return target
+ .pipe(inject(bowerFiles, {name: 'bower', ignorePath: config.bowerFiles.replace('.', ''), addPrefix: 'bower_files'}))
+ .pipe(inject(appFiles, {ignorePath: config.build.replace('.', '')}))
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
.pipe(gulp.dest(config.build));
});
diff --git a/gulp/build/scripts.js b/gulp/build/scripts.js
index fd646e9d..fbe9ab23 100644
--- a/gulp/build/scripts.js
+++ b/gulp/build/scripts.js
@@ -11,15 +11,23 @@ var gulp = require('gulp'),
gulp.task('clean:scripts', function() {
return del([
config.build + '**/*.js',
+<<<<<<< HEAD
'!' + config.build + '**/app.config.js'
+=======
+ '!' + config.build + '**/app.constants.js'
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
]);
});
gulp.task('scripts', ['clean:scripts'], function() {
return gulp
.src([].concat(
+<<<<<<< HEAD
config.scripts,
config.components.scripts
+=======
+ config.scripts
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
))
.pipe(cache(config.jsCache))
.pipe(ngAnnotate())
@@ -31,8 +39,12 @@ gulp.task('scripts', ['clean:scripts'], function() {
gulp.task('rebuild-scripts', function() {
return gulp
.src([].concat(
+<<<<<<< HEAD
config.scripts,
config.components.scripts
+=======
+ config.scripts
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
))
.pipe(cache('jsscripts'))
.pipe(ngAnnotate())
diff --git a/gulp/build/serve-build.js b/gulp/build/serve-build.js
index 6aa9133e..8bfd567f 100644
--- a/gulp/build/serve-build.js
+++ b/gulp/build/serve-build.js
@@ -1,21 +1,31 @@
var gulp = require('gulp'),
config = require('../../gulp.config'),
+<<<<<<< HEAD
browserSync = require('browser-sync'),
+=======
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
argv = require('yargs')
.count('debug')
.alias('d', 'debug')
.argv,
+<<<<<<< HEAD
serve = require('../serve'),
unit = require('../test/unit'),
plato = require('../test/plato');
+=======
+ serve = require('../serve');
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
gulp.task('serve-build', ['inject'], function() {
serve(true /*isDev*/);
if (argv.debug) {
+<<<<<<< HEAD
unit.RunUnitTests();
unit.ServeTests();
plato.GenerateReport(function () {
plato.OpenReport();
});
+=======
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
}
});
diff --git a/gulp/build/styles.js b/gulp/build/styles.js
index 620cfbca..25bf547a 100644
--- a/gulp/build/styles.js
+++ b/gulp/build/styles.js
@@ -18,8 +18,12 @@ gulp.task('styles', ['clean:styles'], function() {
return gulp
.src([].concat(
mainBowerFiles({filter: '**/*.less'}),
+<<<<<<< HEAD
config.src + '**/*.less',
config.components.styles.less
+=======
+ './src/app/styles/main.less'
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
))
.pipe(sourcemaps.init())
.pipe(lessImport('oc-import.less'))
diff --git a/gulp/compile/app-css.js b/gulp/compile/app-css.js
index 830c8efd..af8f7bf0 100644
--- a/gulp/compile/app-css.js
+++ b/gulp/compile/app-css.js
@@ -21,9 +21,13 @@ gulp.task('app-css', ['clean:app-css'], function() {
return gulp
.src([].concat(
mainBowerFiles({filter: ['**/*.css', '**/*.less']}),
+<<<<<<< HEAD
config.components.styles.less,
config.components.styles.css,
config.styles
+=======
+ './src/app/styles/main.less'
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
))
.pipe(lessFilter)
.pipe(lessImport('app.less'))
diff --git a/gulp/compile/app-js.js b/gulp/compile/app-js.js
index 6962f758..1928427d 100644
--- a/gulp/compile/app-js.js
+++ b/gulp/compile/app-js.js
@@ -24,10 +24,15 @@ gulp.task('app-js', ['clean:app-js'], function() {
return gulp
.src([].concat(
config.scripts,
+<<<<<<< HEAD
config.components.scripts,
config.templates,
config.components.templates,
config.src + '**/app.config.json'
+=======
+ config.templates,
+ config.src + '**/app.constants.json'
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
))
.pipe(jsonFilter)
.pipe(ngConstant(config.ngConstantSettings))
@@ -40,7 +45,14 @@ gulp.task('app-js', ['clean:app-js'], function() {
.pipe(fileSort())
.pipe(wrapper(config.wrapper))
.pipe(ngAnnotate())
+<<<<<<< HEAD
.pipe(concat('app.js'))
+=======
+ .pipe(concat('app.module.js'))
+ .pipe(concat('app.config.js'))
+ .pipe(concat('app.run.js'))
+ .pipe(concat('app.controller.js'))
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
.pipe(rev())
.pipe(uglify({mangle:false})) //turning off mangle to fix the compile error
.pipe(gulp.dest(config.compile + 'js/'))
diff --git a/gulp/serve.js b/gulp/serve.js
index 3cd7fb3d..c31c88e1 100644
--- a/gulp/serve.js
+++ b/gulp/serve.js
@@ -30,6 +30,7 @@
module.exports = function (isDev) {
if (isDev) {
gulp.watch([].concat(
+<<<<<<< HEAD
config.src + '**/*.html',
config.components.templates
))
@@ -45,6 +46,19 @@
config.components.styles.css
), ['styles']);
gulp.watch(config.src + '**/app.config.json', ['app-config'])
+=======
+ config.src + '**/*.html'
+ ))
+ .on('change', browserSync.reload);
+ gulp.watch([].concat(
+ config.scripts
+ ), ['rebuild-scripts'])
+ .on('change', browserSync.reload);
+ gulp.watch([].concat(
+ config.styles
+ ), ['styles']);
+ gulp.watch(config.src + '**/app.constants.json', ['app-config'])
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
.on('change', browserSync.reload);
}
return nodemon ({
diff --git a/gulp/test/e2e.js b/gulp/test/e2e.js
index 2ae9e302..205ad21a 100644
--- a/gulp/test/e2e.js
+++ b/gulp/test/e2e.js
@@ -1,4 +1,5 @@
var gulp = require('gulp'),
+<<<<<<< HEAD
argv = require('yargs').argv,
selenium = require('selenium-standalone'),
config = require('../../gulp.config'),
@@ -59,5 +60,38 @@ gulp.task('test:e2e', ['http'/*, 'selenium'*/], function() {
.once('end', function() {
browserSync.exit();
selenium.child.kill();
+=======
+ browserSync = require('browser-sync').create(),
+ argv = require('yargs').argv,
+ config = require('../../gulp.config'),
+ protractor = require("gulp-protractor").protractor;
+
+gulp.task('start-localhost', ['inject'], function() {
+ browserSync.init({
+ server: {
+ baseDir: [
+ config.root + config.build.replace('.', ''),
+ config.root + config.src.replace('.', '') + 'app/'
+ ],
+ routes: {
+ '/bower_files': config.root + config.bowerFiles.replace('.', '')
+ }
+ },
+ open: false
+ });
+});
+
+gulp.task('test:e2e', ['start-localhost'], function() {
+ gulp.src([config.src + '**/*.test.js'])
+ .pipe(protractor({
+ configFile: config.root + '/protractor.conf.js',
+ args: [
+ '--suite', argv.suite || 'all'
+ ]
+ }))
+ .on('end', browserSync.exit)
+ .on('error', function (e) {
+ throw e;
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
});
});
diff --git a/gulp/test/unit.js b/gulp/test/unit.js
index bff79e46..d270d494 100644
--- a/gulp/test/unit.js
+++ b/gulp/test/unit.js
@@ -1,4 +1,5 @@
var gulp = require('gulp'),
+<<<<<<< HEAD
inject = require('gulp-inject'),
browserSync = require('browser-sync').create(),
config = require('../../gulp.config'),
@@ -63,3 +64,14 @@ module.exports = {
ServeTests: serveTests
};
+=======
+ config = require('../../gulp.config'),
+ Server = require('karma').Server;
+
+gulp.task('test:unit', ['scripts', 'app-config'], function(done) {
+ new Server({
+ configFile: config.root + '/karma.conf.js',
+ singleRun: true
+ }, done).start();
+});
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
diff --git a/heroku.gulpfile.js b/heroku.gulpfile.js
new file mode 100644
index 00000000..2a1fbc51
--- /dev/null
+++ b/heroku.gulpfile.js
@@ -0,0 +1,12 @@
+'use strict';
+
+var requireDir = require('require-dir'),
+ gulp = require('gulp');
+
+requireDir('./gulp/build', {recurse: false});
+requireDir('./gulp/compile', {recurse: false});
+
+gulp.task('build', ['serve-build']);
+gulp.task('compile', ['serve-compile']);
+
+gulp.task('default', ['inject', 'index']);
\ No newline at end of file
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 00000000..1e8be11b
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,92 @@
+// Karma configuration
+// Generated on Mon Oct 03 2016 09:44:56 GMT-0500 (Central Daylight Time)
+
+module.exports = function(config) {
+ var mainBowerFiles = require('main-bower-files');
+ var path = require('path');
+
+ config.set({
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: __dirname,
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine'],
+
+
+ // list of files / patterns to load in the browser
+ files: [].concat(
+ mainBowerFiles({filter: '**/*.js'}),
+ 'bower_components/angular-mocks/angular-mocks.js',
+ 'build/**/app.module.js',
+ 'build/**/app.*.js',
+ 'build/**/*.js',
+ 'src/**/*.spec.js',
+ '../Components/**/*.spec.js',
+ 'src/app/**/*.html',
+ '../Components/**/*.html'
+ ),
+
+ // list of files to exclude
+ exclude: [
+ 'build/**/*.test.js',
+ '../Components/**/*.test.js',
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ '**/*.html': ['ng-html2js'],
+ '../Components/**/*.html': ['ng-html2js']
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['mocha'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['PhantomJS'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false,
+
+ // Concurrency level
+ // how many browser should be started simultaneous
+ concurrency: Infinity,
+
+ ngHtml2JsPreprocessor: {
+ //stripPrefix: 'src/app/',
+ cacheIdFromPath: function(filepath) {
+ filepath = filepath.replace('src/app', '');
+ filepath = filepath.replace((path.join(__dirname, '../Components/').replace(/\\/g,"/")), '');
+ return filepath;
+ },
+ moduleName: 'orderCloud'
+ }
+ });
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index de2f6623..e178519f 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,11 @@
"name": "ordercloud-seed",
"version": "1.0.0",
"description": "A seed project for custom Four51 Solutions built on AngularJS",
+<<<<<<< HEAD
"main": "app.js",
+=======
+ "main": "app.module.js",
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
"engines": {
"node": "5.6.0",
"npm": "3.6.0"
@@ -12,11 +16,19 @@
"bower_components"
],
"scripts": {
+<<<<<<< HEAD
"postinstall": "bower install && npm run build-all",
"start": "node server.js",
"build-all": "gulp inject index",
"test": "gulp test:unit",
"test-e2e": "gulp test:e2e"
+=======
+ "heroku-prebuild": "npm install bower gulp --save",
+ "postinstall": "bower install && npm run build-all",
+ "start": "node server.js",
+ "build-all": "gulp inject index --gulpfile ./heroku.gulpfile.js",
+ "test": "gulp test:unit"
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
},
"repository": {
"type": "git",
@@ -36,6 +48,7 @@
},
"homepage": "https://github.com/Four51/OrderCloud-Seed-AngularJS#readme",
"dependencies": {
+<<<<<<< HEAD
"bower": "1.7.7",
"browser-sync": "^2.11.1",
"del": "^2.2.0",
@@ -77,3 +90,45 @@
},
"devDependencies": {}
}
+=======
+ "browser-sync": "2.11.1",
+ "del": "2.2.0",
+ "express": "4.13.4",
+ "fs": "0.0.2",
+ "gulp-angular-filesort": "1.1.1",
+ "gulp-angular-templatecache": "1.8.0",
+ "gulp-autoprefixer": "3.1.0",
+ "gulp-beautify": "2.0.0",
+ "gulp-cached": "1.1.0",
+ "gulp-concat": "2.6.0",
+ "gulp-csso": "1.0.1",
+ "gulp-filter": "3.0.1",
+ "gulp-flatten": "0.2.0",
+ "gulp-htmlmin": "1.3.0",
+ "gulp-imagemin": "2.4.0",
+ "gulp-inject": "3.0.0",
+ "gulp-less": "3.0.5",
+ "gulp-less-import": "1.0.0",
+ "gulp-ng-annotate": "1.1.0",
+ "gulp-ng-constant": "1.1.0",
+ "gulp-nodemon": "2.0.6",
+ "gulp-rev": "6.0.1",
+ "gulp-sourcemaps": "1.6.0",
+ "gulp-uglify": "1.5.1",
+ "gulp-wrapper": "1.0.0",
+ "main-bower-files": "2.11.1",
+ "require-dir": "0.3.0",
+ "yargs": "6.6.0"
+ },
+ "devDependencies": {
+ "chromedriver": "2.26.1",
+ "gulp-protractor": "3.0.0",
+ "jasmine-core": "2.5.2",
+ "karma": "1.3.0",
+ "karma-jasmine": "1.1.0",
+ "karma-mocha-reporter": "2.2.1",
+ "karma-ng-html2js-preprocessor": "1.0.0",
+ "karma-phantomjs-launcher": "1.0.2"
+ }
+}
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
diff --git a/protractor.conf.js b/protractor.conf.js
new file mode 100644
index 00000000..1e6011c0
--- /dev/null
+++ b/protractor.conf.js
@@ -0,0 +1,27 @@
+var webdriver = require('chromedriver'),
+ config = require('./gulp.config');
+
+exports.config = {
+ framework: 'jasmine',
+ directConnect: true,
+ baseUrl: 'http://localhost:3000',
+ chromeDriver: webdriver.path,
+ capabilities: {
+ 'browserName': 'chrome'
+ },
+
+ // Spec patterns are relative to the configuration file location passed
+ // to protractor (in this example conf.js).
+ // They may include glob patterns.
+ specs: [
+ config.root + '/src/**/*.test.js',
+ config.root + '/../Components/**/*.test.js'
+ ],
+
+ suites: {
+ all: [
+ config.root + '/src/**/*.test.js',
+ config.root + '/../Components/**/*.test.js'
+ ]
+ }
+};
\ No newline at end of file
diff --git a/server.js b/server.js
index 13d84feb..2abd01a9 100644
--- a/server.js
+++ b/server.js
@@ -16,10 +16,19 @@ switch(env) {
break;
default:
console.log('*** DEV ***');
+<<<<<<< HEAD
app.use(express.static(config.root + config.build.replace('.', '')));
app.use(express.static(config.root + config.src.replace('.', '') + 'app/'));
app.use(express.static(config.root));
app.use(express.static(config.root + config.components.dir));
+=======
+ // Host bower_files
+ app.use('/bower_files', express.static(config.root + config.bowerFiles.replace('.', '')));
+ // Host unminfied javascript files
+ app.use(express.static(config.root + config.build.replace('.', '')));
+ // Host unchanged html files
+ app.use(express.static(config.root + config.src.replace('.', '') + 'app/'));
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
app.get('/*', function(req, res) {
res.sendFile(config.root + config.build.replace('.', '') + 'index.html');
});
diff --git a/src/README.md b/src/README.md
index 78fbf9aa..ae4adef7 100644
--- a/src/README.md
+++ b/src/README.md
@@ -8,12 +8,25 @@ tests of such code.
```
src/
|- app/
+<<<<<<< HEAD
| |- home/
| |- app.js
| |- app.spec.js
| |- global.less
| |- variables.less
|- assets/
+=======
+ | |- common/
+ | |- styles/
+ | |- app.config.js
+ | |- app.constants.json
+ | |- app.controller.js
+ | |- app.module.js
+ | |- app.run.js
+ | |- app.spec.js
+ |- assets/
+ | |- images/
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
|- index.html
```
diff --git a/src/app/README.md b/src/app/README.md
index e0db0fa9..6ec439ab 100644
--- a/src/app/README.md
+++ b/src/app/README.md
@@ -5,14 +5,26 @@
```
src/
|- app/
+<<<<<<< HEAD
| |- home/
| |- about/
| |- app.js
+=======
+ | |- app.config.js
+ | |- app.constants.json
+ | |- app.controller.js
+ | |- app.module.js
+ | |- app.run.js
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
| |- app.spec.js
```
The `src/app` directory contains all code specific to this application. Apart
+<<<<<<< HEAD
from `app.js` and its accompanying tests (discussed below), this directory is
+=======
+from `app.*.js` and its accompanying tests (discussed below), this directory is
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
filled with subdirectories corresponding to high-level sections of the
application, often corresponding to top-level routes. Each directory can have as
many subdirectories as it needs, and the build system will understand what to
@@ -22,9 +34,15 @@ route `/products`, though this is in no way enforced. Products may then have
subdirectories for "create", "view", "search", etc. The "view" submodule may
then define a route of `/products/:id`, ad infinitum.
+<<<<<<< HEAD
## `app.js`
This is our main app configuration file. It kickstarts the whole process by
+=======
+## `app.module.js`
+
+This is our main app file. It kickstarts the whole process by
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
requiring all the modules that we need.
By default, the OrderCloud AngularJS Seed includes a few useful modules written
@@ -50,9 +68,17 @@ angular.module('orderCloud', [
])
```
+<<<<<<< HEAD
With app modules broken down in this way, all routing is performed by the
submodules we include, as that is where our app's functionality is really
defined. So all we need to do in `app.js` is specify a default route to follow,
+=======
+## `app.config.js`
+
+With app modules broken down in this way, all routing is performed by the
+submodules we include, as that is where our app's functionality is really
+defined. So all we need to do in `app.config.js` is specify a default route to follow,
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
which route of course is defined in a submodule. In this case, our `home` module
is where we want to start, which has a defined route for `/home` in
`src/app/home/home.js`.
@@ -63,6 +89,11 @@ is where we want to start, which has a defined route for `/home` in
})
```
+<<<<<<< HEAD
+=======
+## `app.run.js`
+
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
Use the main applications run method to execute any code after services
have been instantiated.
@@ -71,6 +102,11 @@ have been instantiated.
})
```
+<<<<<<< HEAD
+=======
+## `app.controller.js`
+
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
And then we define our main application controller. This is a good place for logic
not specific to the template or route, such as menu logic or page title wiring.
@@ -88,13 +124,20 @@ not specific to the template or route, such as menu logic or page title wiring.
One of the design philosophies of `OrderCloud-Seed-AngularJS` is that tests should exist
alongside the code they test and that the build system should be smart enough to
+<<<<<<< HEAD
know the difference and react accordingly. As such, the unit test for `app.js`
+=======
+know the difference and react accordingly. As such, the unit test for `app.*.js`
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
is `app.spec.js`, though it is quite minimal.
### Global application styles
+<<<<<<< HEAD
By default, we include [Ambient](http://ionlyseespots.github.io/ambient-design/index.html) which is an internally developed design framework that makes use of HTML5 elements & CSS3 attributes to layout the document outline.
+=======
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
Within the `src/app/` directory we included a `global.less` and `variables.less` file.
These should be utilized for application wide LESS variables and mixins. Each component
within `src/app` will have a corresponding `less/` directory with a similar structure.
diff --git a/src/app/account/account.js b/src/app/account/account.js
index a7584e5e..ac6bebfd 100644
--- a/src/app/account/account.js
+++ b/src/app/account/account.js
@@ -1,9 +1,17 @@
angular.module('orderCloud')
.config(AccountConfig)
+<<<<<<< HEAD
.controller('AccountCtrl', AccountController)
.factory('AccountService', AccountService)
.controller('ConfirmPasswordCtrl', ConfirmPasswordController)
.controller('ChangePasswordCtrl', ChangePasswordController)
+=======
+ .controller('AccountInfoCtrl', AccountInfoController)
+ .controller('AccountEditModalCtrl', AccountEditModalController)
+ .factory('AccountService', AccountService)
+ .controller('ChangePasswordModalCtrl', ChangePasswordModalController)
+ .controller('ConfirmPasswordCtrl', ConfirmPasswordController)
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
;
function AccountConfig($stateProvider) {
@@ -12,6 +20,7 @@ function AccountConfig($stateProvider) {
parent: 'base',
url: '/account',
templateUrl: 'account/templates/account.tpl.html',
+<<<<<<< HEAD
controller: 'AccountCtrl',
controllerAs: 'account'
})
@@ -20,11 +29,22 @@ function AccountConfig($stateProvider) {
templateUrl: 'account/templates/changePassword.tpl.html',
controller: 'ChangePasswordCtrl',
controllerAs: 'changePassword'
+=======
+ controller: 'AccountInfoCtrl',
+ controllerAs: 'accountInfo',
+ data: {
+ pageTitle: "Account"
+ }
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
})
;
}
+<<<<<<< HEAD
function AccountService($q, $uibModal, OrderCloud) {
+=======
+function AccountService($q, $uibModal, OrderCloud, toastr) {
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
var service = {
Update: _update,
ChangePassword: _changePassword
@@ -48,15 +68,27 @@ function AccountService($q, $uibModal, OrderCloud) {
templateUrl: 'account/templates/confirmPassword.modal.tpl.html',
controller: 'ConfirmPasswordCtrl',
controllerAs: 'confirmPassword',
+<<<<<<< HEAD
size: 'sm'
+=======
+ size: 'md'
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
}).result.then(function(password) {
var checkPasswordCredentials = {
Username: currentProfile.Username,
Password: password
};
+<<<<<<< HEAD
OrderCloud.Auth.GetToken(checkPasswordCredentials)
.then(function() {
updateUser();
+=======
+
+ OrderCloud.Auth.GetToken(checkPasswordCredentials)
+ .then(function() {
+ updateUser();
+ toastr.success('Account changes were saved.', 'Success!');
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
})
.catch(function(ex) {
deferred.reject(ex);
@@ -98,9 +130,56 @@ function AccountService($q, $uibModal, OrderCloud) {
return service;
}
+<<<<<<< HEAD
function AccountController($exceptionHandler, toastr, AccountService, CurrentUser) {
var vm = this;
vm.profile = angular.copy(CurrentUser);
+=======
+function AccountInfoController($uibModal, CurrentUser){
+ var vm = this;
+ vm.profile = angular.copy(CurrentUser);
+ vm.currentUser = CurrentUser;
+
+ vm.editInfo = function(){
+ $uibModal.open({
+ animation: true,
+ templateUrl: 'account/templates/accountSettings.modal.tpl.html',
+ controller: 'AccountEditModalCtrl',
+ controllerAs: 'accountEditModal',
+ backdrop:'static',
+ size: 'md',
+ resolve: {
+ Profile: function(){
+ return vm.profile;
+ },
+ CurrentUser: function(){
+ return vm.currentUser;
+ }
+ }
+ });
+ };
+
+ vm.changePassword = function(user){
+ $uibModal.open({
+ animation: true,
+ templateUrl: 'account/templates/changePassword.modal.tpl.html',
+ controller: 'ChangePasswordModalCtrl',
+ controllerAs: 'changePasswordModal',
+ backdrop:'static',
+ size: 'md',
+ resolve: {
+ CurrentUser: function(){
+ return user;
+ }
+ }
+ });
+ };
+}
+
+function AccountEditModalController($uibModalInstance, $exceptionHandler, AccountService, CurrentUser, Profile){
+ var vm = this;
+ vm.profile = Profile;
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
var currentProfile = CurrentUser;
vm.update = function() {
@@ -108,11 +187,19 @@ function AccountController($exceptionHandler, toastr, AccountService, CurrentUse
.then(function(data) {
vm.profile = angular.copy(data);
currentProfile = data;
+<<<<<<< HEAD
toastr.success('Account changes were saved.', 'Success!');
})
.catch(function(ex) {
vm.profile = currentProfile;
$exceptionHandler(ex)
+=======
+ vm.submit();
+ })
+ .catch(function(ex) {
+ vm.profile = currentProfile;
+ $exceptionHandler(ex);
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
});
};
@@ -120,6 +207,7 @@ function AccountController($exceptionHandler, toastr, AccountService, CurrentUse
vm.profile = currentProfile;
form.$setPristine(true);
};
+<<<<<<< HEAD
}
function ConfirmPasswordController($uibModalInstance) {
@@ -127,6 +215,11 @@ function ConfirmPasswordController($uibModalInstance) {
vm.submit = function() {
$uibModalInstance.close(vm.password);
+=======
+
+ vm.submit = function() {
+ $uibModalInstance.close();
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
};
vm.cancel = function() {
@@ -134,7 +227,11 @@ function ConfirmPasswordController($uibModalInstance) {
};
}
+<<<<<<< HEAD
function ChangePasswordController($state, $exceptionHandler, toastr, AccountService, CurrentUser) {
+=======
+function ChangePasswordModalController(toastr, $state, $exceptionHandler, AccountService, $uibModalInstance, CurrentUser){
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
var vm = this;
vm.currentUser = CurrentUser;
@@ -145,6 +242,7 @@ function ChangePasswordController($state, $exceptionHandler, toastr, AccountServ
vm.currentUser.CurrentPassword = null;
vm.currentUser.NewPassword = null;
vm.currentUser.ConfirmPassword = null;
+<<<<<<< HEAD
$state.go('account');
})
.catch(function(ex) {
@@ -152,3 +250,33 @@ function ChangePasswordController($state, $exceptionHandler, toastr, AccountServ
});
};
}
+=======
+ vm.submit();
+ $state.go('account.information');
+ })
+ .catch(function(ex) {
+ $exceptionHandler(ex);
+ });
+ };
+
+ vm.submit = function() {
+ $uibModalInstance.close();
+ };
+
+ vm.cancel = function() {
+ $uibModalInstance.dismiss('cancel');
+ };
+}
+
+function ConfirmPasswordController($uibModalInstance) {
+ var vm = this;
+
+ vm.submit = function() {
+ $uibModalInstance.close(vm.password);
+ };
+
+ vm.cancel = function() {
+ $uibModalInstance.dismiss('cancel');
+ };
+}
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
diff --git a/src/app/account/templates/account.tpl.html b/src/app/account/templates/account.tpl.html
index 51f08221..cbcad4c1 100644
--- a/src/app/account/templates/account.tpl.html
+++ b/src/app/account/templates/account.tpl.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
-
\ No newline at end of file
+
+=======
+
+
+
+ My Account
+
+
+
+
+
+
Name: {{accountInfo.profile.FirstName + ' ' + accountInfo.profile.LastName}}
+ Email Address: {{accountInfo.profile.Email}}
+ Username: {{accountInfo.profile.Username}}
+ Phone Number: {{accountInfo.profile.Phone}}
+
+
+
+ Edit Profile
+ Change Password
+
+
+
+
+
+
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
diff --git a/src/app/account/templates/accountSettings.modal.tpl.html b/src/app/account/templates/accountSettings.modal.tpl.html
new file mode 100644
index 00000000..eea319d9
--- /dev/null
+++ b/src/app/account/templates/accountSettings.modal.tpl.html
@@ -0,0 +1,39 @@
+
diff --git a/src/app/account/templates/changePassword.modal.tpl.html b/src/app/account/templates/changePassword.modal.tpl.html
new file mode 100644
index 00000000..32b9e297
--- /dev/null
+++ b/src/app/account/templates/changePassword.modal.tpl.html
@@ -0,0 +1,27 @@
+
diff --git a/src/app/account/templates/confirmPassword.modal.tpl.html b/src/app/account/templates/confirmPassword.modal.tpl.html
index c3791fad..9a671667 100644
--- a/src/app/account/templates/confirmPassword.modal.tpl.html
+++ b/src/app/account/templates/confirmPassword.modal.tpl.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
+=======
+
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
diff --git a/src/app/account/tests/account.spec.js b/src/app/account/tests/account.spec.js
index 7d0a210f..102c4c76 100644
--- a/src/app/account/tests/account.spec.js
+++ b/src/app/account/tests/account.spec.js
@@ -2,7 +2,12 @@ describe('Component: Account', function() {
var scope,
q,
account,
+<<<<<<< HEAD
accountFactory;
+=======
+ accountFactory,
+ uibModalInstance;
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
beforeEach(module('orderCloud'));
beforeEach(module('orderCloud.sdk'));
beforeEach(inject(function($q, $rootScope, AccountService) {
@@ -19,6 +24,10 @@ describe('Component: Account', function() {
Active: true
};
accountFactory = AccountService;
+<<<<<<< HEAD
+=======
+ uibModalInstance = jasmine.createSpyObj('modalInstance', ['close', 'dismiss', 'result.then']);
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
}));
describe('Factory: AccountService', function() {
@@ -72,6 +81,7 @@ describe('Component: Account', function() {
});
});
+<<<<<<< HEAD
describe('Controller: AccountCtrl', function() {
var accountCtrl, currentProfile;
beforeEach(inject(function($state, $controller) {
@@ -155,11 +165,113 @@ describe('Component: Account', function() {
changePasswordCtrl = $controller('ChangePasswordCtrl', {
$scope: scope,
CurrentUser: {}
+=======
+ describe('Controller: AccountInfoCtrl', function() {
+ var accountInfoCtrl,
+ uibModal,
+ actualOptions;
+ beforeEach(inject(function ($uibModal, $controller) {
+ accountInfoCtrl = $controller('AccountInfoCtrl', {
+ CurrentUser: {},
+ Profile: {}
+ });
+ uibModal = $uibModal;
+ accountInfoCtrl.editInfoModalOptions = {
+ animation: true,
+ templateUrl: 'account/templates/accountSettings.modal.tpl.html',
+ controller: 'AccountEditModalCtrl',
+ controllerAs: 'accountEditModal',
+ backdrop: 'static',
+ size: 'md',
+ resolve: {
+ Profile: jasmine.any(Function),
+ CurrentUser: jasmine.any(Function)
+ }
+ };
+ accountInfoCtrl.changePasswordModalOptions = {
+ animation: true,
+ templateUrl: 'account/templates/changePassword.modal.tpl.html',
+ controller: 'ChangePasswordModalCtrl',
+ controllerAs: 'changePasswordModal',
+ backdrop:'static',
+ size: 'md',
+ resolve: {
+ CurrentUser: jasmine.any(Function)
+ }
+ };
+ }));
+ describe('editInfo', function() {
+ it('should call the $uibModal open with editInfo modal', function() {
+ spyOn(uibModal, 'open').and.callFake(function(options) {
+ actualOptions = options;
+ return uibModalInstance;
+ });
+ accountInfoCtrl.editInfo();
+ expect(uibModal.open).toHaveBeenCalledWith(accountInfoCtrl.editInfoModalOptions);
+ });
+ });
+ describe('changePassword', function() {
+ it('should call the $uibModal open with changePassword modal', function() {
+ spyOn(uibModal, 'open').and.callFake(function(options) {
+ actualOptions = options;
+ return uibModalInstance;
+ });
+ accountInfoCtrl.changePassword();
+ expect(uibModal.open).toHaveBeenCalledWith(accountInfoCtrl.changePasswordModalOptions);
+ });
+ });
+ });
+
+ describe('Controller: AccountEditModalCtrl', function() {
+ var accountEditModalCtrl, currentProfile;
+ beforeEach(inject(function($state, $controller) {
+ accountEditModalCtrl = $controller('AccountEditModalCtrl', {
+ $uibModalInstance: uibModalInstance,
+ CurrentUser: {},
+ Profile: {}
+ });
+ }));
+ describe('update', function() {
+ beforeEach(inject(function() {
+ currentProfile = {};
+ accountEditModalCtrl.Profile = {};
+ var defer = q.defer();
+ defer.resolve();
+ spyOn(accountFactory, 'Update').and.returnValue(defer.promise);
+ accountEditModalCtrl.update();
+ }));
+ it('should call the Accounts Update method', inject(function() {
+ expect(accountFactory.Update).toHaveBeenCalledWith(currentProfile, accountEditModalCtrl.Profile);
+ }));
+ });
+ describe('submit', function() {
+ it('should close the modal', function() {
+ accountEditModalCtrl.submit();
+ expect(uibModalInstance.close).toHaveBeenCalled();
+ });
+ });
+ describe('cancel', function() {
+ it('should dismiss the modal', function() {
+ accountEditModalCtrl.cancel();
+ expect(uibModalInstance.dismiss).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('Controller: ChangePasswordModalCtrl', function () {
+ var changePasswordModalCtrl;
+ beforeEach(inject(function($state, $controller) {
+ changePasswordModalCtrl = $controller('ChangePasswordModalCtrl', {
+ $scope: scope,
+ CurrentUser: {},
+ $uibModalInstance: uibModalInstance
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
});
spyOn($state, 'go').and.returnValue(true);
}));
describe('changePassword', function() {
beforeEach(inject(function() {
+<<<<<<< HEAD
changePasswordCtrl.currentUser = account;
var defer = q.defer();
defer.resolve(account);
@@ -171,6 +283,51 @@ describe('Component: Account', function() {
}));
});
+=======
+ changePasswordModalCtrl.currentUser = account;
+ var defer = q.defer();
+ defer.resolve(account);
+ spyOn(accountFactory, 'ChangePassword').and.returnValue(defer.promise);
+ changePasswordModalCtrl.changePassword();
+ }));
+ it ('should call the Accounts ChangePassword method', inject(function() {
+ expect(accountFactory.ChangePassword).toHaveBeenCalledWith(changePasswordModalCtrl.currentUser);
+ }));
+ });
+ describe('submit', function() {
+ it('should close the modal', function() {
+ changePasswordModalCtrl.submit();
+ expect(uibModalInstance.close).toHaveBeenCalled();
+ });
+ });
+ describe('cancel', function() {
+ it('should dismiss the modal', function(){
+ changePasswordModalCtrl.cancel();
+ expect(uibModalInstance.dismiss).toHaveBeenCalled();
+ });
+ });
+ });
+ describe('Controller: ConfirmPasswordCtrl', function () {
+ var confirmPasswordCtrl;
+ beforeEach(inject(function($controller) {
+ confirmPasswordCtrl = $controller('ConfirmPasswordCtrl', {
+ $uibModalInstance: uibModalInstance,
+ password: 'fakepassword'
+ });
+ }));
+ describe('submit', function() {
+ it('should close the modal after confirming password', function() {
+ confirmPasswordCtrl.submit();
+ expect(uibModalInstance.close).toHaveBeenCalledWith(confirmPasswordCtrl.password);
+ });
+ });
+ describe('cancel', function() {
+ it('should dismiss the modal', function() {
+ confirmPasswordCtrl.cancel();
+ expect(uibModalInstance.dismiss).toHaveBeenCalled();
+ });
+ });
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
});
});
diff --git a/src/app/addPromotion/addPromotion.js b/src/app/addPromotion/addPromotion.js
new file mode 100644
index 00000000..1afffa8c
--- /dev/null
+++ b/src/app/addPromotion/addPromotion.js
@@ -0,0 +1,22 @@
+angular.module('orderCloud')
+ .component('ocAddPromotion', {
+ templateUrl: 'addPromotion/templates/addPromotion.tpl.html',
+ bindings: {
+ order: '<'
+ },
+ controller: AddPromotionComponentCtrl
+ });
+
+function AddPromotionComponentCtrl($exceptionHandler, $rootScope, OrderCloud, toastr) {
+ this.submit = function(orderID, promoCode) {
+ OrderCloud.Orders.AddPromotion(orderID, promoCode)
+ .then(function(promo) {
+ $rootScope.$broadcast('OC:UpdatePromotions', orderID);
+ $rootScope.$broadcast('OC:UpdateOrder', orderID);
+ toastr.success('Promo code '+ promo.Code + ' added!', 'Success');
+ })
+ .catch(function(err) {
+ $exceptionHandler(err);
+ });
+ };
+}
\ No newline at end of file
diff --git a/src/app/addPromotion/templates/addPromotion.tpl.html b/src/app/addPromotion/templates/addPromotion.tpl.html
new file mode 100644
index 00000000..efea60e3
--- /dev/null
+++ b/src/app/addPromotion/templates/addPromotion.tpl.html
@@ -0,0 +1,9 @@
+
+
Promotion Code
+
+
+
+ Apply
+
+
+
\ No newline at end of file
diff --git a/src/app/addPromotion/tests/addPromotion.spec.js b/src/app/addPromotion/tests/addPromotion.spec.js
new file mode 100644
index 00000000..5f7a55d0
--- /dev/null
+++ b/src/app/addPromotion/tests/addPromotion.spec.js
@@ -0,0 +1,50 @@
+describe('Component: addPromotion', function(){
+ var scope,
+ rootScope,
+ q,
+ oc
+ ;
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(inject(function($rootScope, $q, OrderCloud){
+ scope = $rootScope.$new();
+ rootScope = $rootScope;
+ q = $q;
+ oc = OrderCloud;
+ }));
+ describe('Component Directive: ocAddPromotion', function(){
+ var addPromotionCtrl,
+ toaster
+ ;
+ beforeEach(inject(function($componentController, toastr){
+ toaster = toastr;
+ addPromotionCtrl = $componentController('ocAddPromotion', {
+ $scope: scope,
+ $rootScope: rootScope,
+ OrderCloud: oc,
+ toastr: toaster
+ });
+ }));
+ describe('submit', function(){
+ var mockPromo,
+ mockOrderID;
+ beforeEach(function(){
+ mockPromo = {Code:'Discount10'};
+ mockOrderID = 'order123';
+
+ var defer = q.defer();
+ defer.resolve(mockPromo);
+ spyOn(oc.Orders, 'AddPromotion').and.returnValue(defer.promise);
+ spyOn(rootScope, '$broadcast');
+ spyOn(toaster, 'success');
+ addPromotionCtrl.submit(mockOrderID, mockPromo.Code);
+ });
+ it('should call Orders.AddPromotion', function(){
+ expect(oc.Orders.AddPromotion).toHaveBeenCalledWith(mockOrderID, mockPromo.Code);
+ scope.$digest();
+ expect(rootScope.$broadcast).toHaveBeenCalledWith('OC:UpdatePromotions', mockOrderID);
+ expect(toaster.success).toHaveBeenCalledWith('Promo code ' + mockPromo.Code + ' added!', 'Success');
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/app/app.config.js b/src/app/app.config.js
new file mode 100644
index 00000000..f28d1183
--- /dev/null
+++ b/src/app/app.config.js
@@ -0,0 +1,38 @@
+angular.module('orderCloud')
+ .config(AppConfig)
+;
+
+function AppConfig($urlRouterProvider, $urlMatcherFactoryProvider, $locationProvider, defaultstate, $qProvider, $provide, $httpProvider) {
+ //Routing
+ $locationProvider.html5Mode(true);
+ $urlMatcherFactoryProvider.strictMode(false);
+ $urlRouterProvider.otherwise(function ($injector) {
+ var $state = $injector.get('$state');
+ $state.go(defaultstate); //Set the default state name in app.constants.json
+ });
+
+ //Error Handling
+ $provide.decorator('$exceptionHandler', handler);
+ $qProvider.errorOnUnhandledRejections(false); //Stop .catch validation from angular v1.6.0
+ function handler($delegate, $injector) { //Catch all for unhandled errors
+ return function(ex, cause) {
+ $delegate(ex, cause);
+ $injector.get('toastr').error(ex.data ? (ex.data.error || (ex.data.Errors ? ex.data.Errors[0].Message : ex.data)) : ex.message, 'Error');
+ };
+ }
+
+ //HTTP Interceptor for OrderCloud Authentication
+ $httpProvider.interceptors.push(function($q, $rootScope) {
+ return {
+ 'responseError': function(rejection) {
+ if (rejection.config.url.indexOf('ordercloud.io') > -1 && rejection.status == 401) {
+ $rootScope.$broadcast('OC:AccessInvalidOrExpired'); //Trigger RememberMe || AuthAnonymous in AppCtrl
+ }
+ if (rejection.config.url.indexOf('ordercloud.io') > -1 && rejection.status == 403){
+ $rootScope.$broadcast('OC:AccessForbidden'); //Trigger warning toastr message for insufficient permissions
+ }
+ return $q.reject(rejection);
+ }
+ };
+ });
+}
\ No newline at end of file
diff --git a/src/app/app.constants.json b/src/app/app.constants.json
new file mode 100644
index 00000000..ea190d0d
--- /dev/null
+++ b/src/app/app.constants.json
@@ -0,0 +1,10 @@
+{
+ "appname": "Angular Buyer",
+ "scope": "FullAccess",
+ "clientid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
+ "buyerid": "XXXX",
+ "catalogid": "XXXX",
+ "environment": "prod",
+ "defaultstate": "home",
+ "anonymous": false
+}
\ No newline at end of file
diff --git a/src/app/app.controller.js b/src/app/app.controller.js
new file mode 100644
index 00000000..d53155a6
--- /dev/null
+++ b/src/app/app.controller.js
@@ -0,0 +1,57 @@
+angular.module('orderCloud')
+ .controller('AppCtrl', AppController)
+;
+
+function AppController($q, $rootScope, $state, $ocMedia, toastr, LoginService, appname, anonymous, defaultstate) {
+ var vm = this;
+ vm.name = appname;
+ vm.$state = $state;
+ vm.$ocMedia = $ocMedia;
+ vm.stateLoading = undefined;
+
+ function cleanLoadingIndicators() {
+ if (vm.stateLoading && vm.stateLoading.promise && !vm.stateLoading.promise.$cgBusyFulfilled) vm.stateLoading.resolve(); //resolve leftover loading promises
+ }
+
+ //Detect if the app was loaded on a touch device with relatively good certainty
+ //http://stackoverflow.com/a/6262682
+ vm.isTouchDevice = (function() {
+ var el = document.createElement('div');
+ el.setAttribute('ongesturestart', 'return;'); // or try "ontouchstart"
+ return typeof el.ongesturestart === "function";
+ })();
+
+ vm.logout = function() {
+ LoginService.Logout();
+ };
+
+ $rootScope.$on('$stateChangeStart', function(e, toState) {
+ cleanLoadingIndicators();
+ var defer = $q.defer();
+ if (toState.data) defer.message = toState.data.loadingMessage;
+ vm.stateLoading = defer;
+ });
+
+ $rootScope.$on('$stateChangeSuccess', function() {
+ cleanLoadingIndicators();
+ });
+
+ $rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {
+ if (toState.name == defaultstate) event.preventDefault(); //prevent infinite loop when error occurs on default state (otherwise in Routing config)
+ cleanLoadingIndicators();
+ console.log(error);
+ });
+
+ $rootScope.$on('OC:AccessInvalidOrExpired', function() {
+ if (anonymous) {
+ cleanLoadingIndicators();
+ LoginService.AuthAnonymous();
+ } else {
+ LoginService.RememberMe();
+ }
+ });
+
+ $rootScope.$on('OC:AccessForbidden', function(){
+ toastr.warning("You do not have permission to access this page.");
+ });
+}
\ No newline at end of file
diff --git a/src/app/app.module.js b/src/app/app.module.js
new file mode 100644
index 00000000..5dbf7981
--- /dev/null
+++ b/src/app/app.module.js
@@ -0,0 +1,17 @@
+angular.module('orderCloud', [
+ 'ngSanitize',
+ 'ngAnimate',
+ 'ngMessages',
+ 'ngTouch',
+ 'ui.tree',
+ 'ui.router',
+ 'ui.bootstrap',
+ 'orderCloud.sdk',
+ 'LocalForageModule',
+ 'toastr',
+ 'angular-busy',
+ 'jcs-autoValidate',
+ 'treeControl',
+ 'hl.sticky'
+ ]
+);
\ No newline at end of file
diff --git a/src/app/app.run.js b/src/app/app.run.js
new file mode 100644
index 00000000..a784fefe
--- /dev/null
+++ b/src/app/app.run.js
@@ -0,0 +1,24 @@
+angular.module('orderCloud')
+ .run(AppRun)
+;
+
+function AppRun(OrderCloud, catalogid, uibDatepickerConfig, uibDatepickerPopupConfig, defaultErrorMessageResolver) {
+ //Set Default CatalogID
+ catalogid ? OrderCloud.CatalogID.Set(catalogid) : OrderCloud.CatalogID.Set(OrderCloud.BuyerID.Get());
+
+ //Default Datepicker Options
+ uibDatepickerConfig.showWeeks = false;
+ uibDatepickerPopupConfig.showButtonBar = false;
+
+ //Set Custom Error Messages for angular-auto-validate --- http://jonsamwell.github.io/angular-auto-validate/ ---
+ defaultErrorMessageResolver.getErrorMessages().then(function (errorMessages) {
+ errorMessages['customPassword'] = 'Password must be at least eight characters long and include at least one letter and one number';
+ //regex for customPassword = ^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d!$%@#£€*?&]{8,}$
+ errorMessages['positiveInteger'] = 'Please enter a positive integer';
+ //regex positiveInteger = ^[0-9]*[1-9][0-9]*$
+ errorMessages['ID_Name'] = 'Only Alphanumeric characters, hyphens and underscores are allowed';
+ //regex ID_Name = ([A-Za-z0-9\-\_]+)
+ errorMessages['confirmpassword'] = 'Your passwords do not match';
+ errorMessages['noSpecialChars'] = 'Only Alphanumeric characters are allowed';
+ });
+}
\ No newline at end of file
diff --git a/src/app/base/base.js b/src/app/base/base.js
index 40debf94..a9a89f96 100644
--- a/src/app/base/base.js
+++ b/src/app/base/base.js
@@ -1,6 +1,7 @@
angular.module('orderCloud')
.config(BaseConfig)
.controller('BaseCtrl', BaseController)
+<<<<<<< HEAD
.filter('occomponents', occomponents)
;
@@ -164,3 +165,114 @@ function occomponents() {
return result;
}
}
+=======
+ .factory('NewOrder', NewOrderService)
+;
+
+function BaseConfig($stateProvider) {
+ $stateProvider.state('base', {
+ url: '',
+ abstract: true,
+ views: {
+ '': {
+ templateUrl: 'base/templates/base.tpl.html',
+ controller: 'BaseCtrl',
+ controllerAs: 'base'
+ },
+ 'nav@base': {
+ 'templateUrl': 'base/templates/navigation.tpl.html'
+ }
+ },
+ resolve: {
+ CurrentUser: function($q, $state, OrderCloud, buyerid) {
+ return OrderCloud.Me.Get()
+ .then(function(data) {
+ OrderCloud.BuyerID.Set(buyerid);
+ return data;
+ })
+ },
+ ExistingOrder: function($q, OrderCloud, CurrentUser) {
+ return OrderCloud.Me.ListOutgoingOrders(null, 1, 1, null, "!DateCreated", {Status:"Unsubmitted"})
+ .then(function(data) {
+ return data.Items[0];
+ });
+ },
+ CurrentOrder: function(ExistingOrder, NewOrder, CurrentUser) {
+ if (!ExistingOrder) {
+ return NewOrder.Create({});
+ } else {
+ return ExistingOrder;
+ }
+ },
+ AnonymousUser: function($q, OrderCloud, CurrentUser) {
+ CurrentUser.Anonymous = angular.isDefined(JSON.parse(atob(OrderCloud.Auth.ReadToken().split('.')[1])).orderid);
+ }
+ }
+ });
+}
+
+function BaseController($rootScope, $state, ProductSearch, CurrentUser, CurrentOrder, OrderCloud) {
+ var vm = this;
+ vm.currentUser = CurrentUser;
+ vm.currentOrder = CurrentOrder;
+
+ vm.mobileSearch = function() {
+ ProductSearch.Open()
+ .then(function(data) {
+ if (data.productID) {
+ $state.go('productDetail', {productid: data.productID});
+ } else {
+ $state.go('productSearchResults', {searchTerm: data.searchTerm});
+ }
+ });
+ };
+
+ $rootScope.$on('OC:UpdateOrder', function(event, OrderID, message) {
+ vm.orderLoading = {
+ message: message
+ };
+ vm.orderLoading.promise = OrderCloud.Orders.Get(OrderID)
+ .then(function(data) {
+ vm.currentOrder = data;
+ });
+ });
+}
+
+function NewOrderService($q, OrderCloud) {
+ var service = {
+ Create: _create
+ };
+
+ function _create() {
+ var deferred = $q.defer();
+ var order = {};
+
+ //ShippingAddressID
+ OrderCloud.Me.ListAddresses(null, 1, 100, null, null, {Shipping: true})
+ .then(function(shippingAddresses) {
+ if (shippingAddresses.Items.length) order.ShippingAddressID = shippingAddresses.Items[0].ID;
+ setBillingAddress();
+ });
+
+ //BillingAddressID
+ function setBillingAddress() {
+ OrderCloud.Me.ListAddresses(null, 1, 100, null, null, {Billing: true})
+ .then(function(billingAddresses) {
+ if (billingAddresses.Items.length) order.BillingAddressID = billingAddresses.Items[0].ID;
+ createOrder();
+ });
+ }
+
+ function createOrder() {
+ OrderCloud.Orders.Create(order)
+ .then(function(order) {
+ deferred.resolve(order);
+ });
+ }
+
+ return deferred.promise;
+ }
+
+ return service;
+}
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
diff --git a/src/app/base/templates/base.tpl.html b/src/app/base/templates/base.tpl.html
index 33e0ec5e..6161c00f 100644
--- a/src/app/base/templates/base.tpl.html
+++ b/src/app/base/templates/base.tpl.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
@@ -6,3 +7,9 @@
+=======
+
+
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
diff --git a/src/app/base/templates/navigation.tpl.html b/src/app/base/templates/navigation.tpl.html
new file mode 100644
index 00000000..beb1ffc1
--- /dev/null
+++ b/src/app/base/templates/navigation.tpl.html
@@ -0,0 +1,42 @@
+
diff --git a/src/app/base/tests/base.spec.js b/src/app/base/tests/base.spec.js
index feb65e81..00e82c6b 100644
--- a/src/app/base/tests/base.spec.js
+++ b/src/app/base/tests/base.spec.js
@@ -2,6 +2,7 @@ describe('Component: Base', function() {
var q,
scope,
oc,
+<<<<<<< HEAD
underscore;
beforeEach(module('orderCloud'));
beforeEach(module('orderCloud.sdk'));
@@ -48,6 +49,52 @@ describe('Component: Base', function() {
expect(components.nonSpecific).not.toBe(null);
expect(components.buyerSpecific).not.toBe(null);
}));
+=======
+ buyerid = "BUYERID",
+ state,
+ injector;
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(module('ui.router'));
+ beforeEach(inject(function($q, $rootScope, $state, OrderCloud, $injector) {
+ q = $q;
+ scope = $rootScope.$new();
+ oc = OrderCloud;
+ state = $state;
+ injector = $injector;
+ }));
+ describe('State: Base', function() {
+ var base;
+ beforeEach(function() {
+ base = state.get('base');
+ });
+ it ('should attempt to get the current user and set the buyerid', function() {
+ var user = q.defer();
+ user.resolve('TEST USER');
+ spyOn(oc.Me, 'Get').and.returnValue(user.promise);
+ spyOn(oc.BuyerID, 'Set').and.callThrough();
+ injector.invoke(base.resolve.CurrentUser, scope, {$q:q, $state:state, OrderCloud:oc, buyerid:buyerid});
+ expect(oc.Me.Get).toHaveBeenCalled();
+ scope.$digest();
+ expect(oc.BuyerID.Set).toHaveBeenCalledWith('BUYERID');
+ });
+ it ('should search for an existing unsubmitted order', function() {
+ var orderList = q.defer();
+ orderList.resolve({Items:['TEST ORDER']});
+ spyOn(oc.Me, 'ListOutgoingOrders').and.returnValue(orderList.promise);
+ var currentUser = injector.invoke(base.resolve.CurrentUser);
+ injector.invoke(base.resolve.ExistingOrder, scope, {$q:q, OrderCloud:oc, CurrentUser:currentUser});
+ expect(oc.Me.ListOutgoingOrders).toHaveBeenCalledWith(null, 1, 1, null, "!DateCreated", {Status: "Unsubmitted"});
+ });
+ it ('should create a new order if there is not an existing unsubmitted order', inject(function(NewOrder) {
+ var newOrder = NewOrder,
+ existingOrder, //undefined existing order
+ currentUser = injector.invoke(base.resolve.CurrentUser);
+ spyOn(newOrder, 'Create');
+ injector.invoke(base.resolve.CurrentOrder, scope, {ExistingOrder: existingOrder, NewOrder: newOrder, CurrentUser: currentUser});
+ expect(newOrder.Create).toHaveBeenCalledWith({});
+ }))
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
});
describe('Controller: BaseCtrl', function(){
@@ -55,6 +102,7 @@ describe('Component: Base', function() {
fake_user = {
Username: 'notarealusername',
Password: 'notarealpassword'
+<<<<<<< HEAD
};
beforeEach(inject(function($controller) {
baseCtrl = $controller('BaseCtrl', {
@@ -91,4 +139,21 @@ describe('Component: Base', function() {
}));
/* No tests needed */
});
+=======
+ },
+ fake_order = {
+ ID: 'fakeorder'
+ };
+ beforeEach(inject(function($controller) {
+ baseCtrl = $controller('BaseCtrl', {
+ CurrentUser: fake_user,
+ CurrentOrder: fake_order
+ });
+ }));
+ it ('should initialize the current user and order into its scope', function() {
+ expect(baseCtrl.currentUser).toBe(fake_user);
+ expect(baseCtrl.currentOrder).toBe(fake_order);
+ });
+ });
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
});
diff --git a/src/app/base/tests/base.test.js b/src/app/base/tests/base.test.js
index a31d445d..dc276cd0 100644
--- a/src/app/base/tests/base.test.js
+++ b/src/app/base/tests/base.test.js
@@ -1,6 +1,10 @@
function BasePage() {
this.get = function() {
+<<<<<<< HEAD
browser.get('/#');
+=======
+ browser.get('#');
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
};
this.getTitle = function() {
@@ -17,7 +21,11 @@ describe('Base', function() {
describe('base', function() {
it ("should display the correct title", function() {
+<<<<<<< HEAD
expect(page.getTitle()).toBe('OrderCloud');
+=======
+ expect(page.getTitle()).toBe('Angular Buyer');
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
});
})
});
\ No newline at end of file
diff --git a/src/app/cart/cart.js b/src/app/cart/cart.js
new file mode 100644
index 00000000..3d87cb49
--- /dev/null
+++ b/src/app/cart/cart.js
@@ -0,0 +1,89 @@
+angular.module('orderCloud')
+ .config(CartConfig)
+ .controller('CartCtrl', CartController)
+;
+
+function CartConfig($stateProvider) {
+ $stateProvider
+ .state('cart', {
+ parent: 'base',
+ url: '/cart',
+ templateUrl: 'cart/templates/cart.tpl.html',
+ controller: 'CartCtrl',
+ controllerAs: 'cart',
+ data: {
+ pageTitle: "Shopping Cart"
+ },
+ resolve: {
+ LineItemsList: function($q, $state, toastr, OrderCloud, ocLineItems, CurrentOrder) {
+ var dfd = $q.defer();
+ OrderCloud.LineItems.List(CurrentOrder.ID)
+ .then(function(data) {
+ if (!data.Items.length) {
+ dfd.resolve(data);
+ }
+ else {
+ ocLineItems.GetProductInfo(data.Items)
+ .then(function() {
+ dfd.resolve(data);
+ });
+ }
+ })
+ .catch(function() {
+ toastr.error('Your order does not contain any line items.', 'Error');
+ dfd.reject();
+ });
+ return dfd.promise;
+ },
+ CurrentPromotions: function(CurrentOrder, OrderCloud) {
+ return OrderCloud.Orders.ListPromotions(CurrentOrder.ID);
+ }
+ }
+ });
+}
+
+function CartController($rootScope, $state, toastr, OrderCloud, LineItemsList, CurrentPromotions, ocConfirm) {
+ var vm = this;
+ vm.lineItems = LineItemsList;
+ vm.promotions = CurrentPromotions.Meta ? CurrentPromotions.Items : CurrentPromotions;
+ vm.removeItem = function(order, scope) {
+ vm.lineLoading = [];
+ vm.lineLoading[scope.$index] = OrderCloud.LineItems.Delete(order.ID, scope.lineItem.ID)
+ .then(function () {
+ $rootScope.$broadcast('OC:UpdateOrder', order.ID);
+ vm.lineItems.Items.splice(scope.$index, 1);
+ toastr.success('Line Item Removed');
+ });
+ };
+
+ //TODO: missing unit tests
+ vm.removePromotion = function(order, scope) {
+ OrderCloud.Orders.RemovePromotion(order.ID, scope.promotion.Code)
+ .then(function() {
+ $rootScope.$broadcast('OC:UpdateOrder', order.ID);
+ vm.promotions.splice(scope.$index, 1);
+ });
+ };
+
+ vm.cancelOrder = function(order){
+ ocConfirm.Confirm("Are you sure you want cancel this order?")
+ .then(function() {
+ OrderCloud.Orders.Delete(order.ID)
+ .then(function(){
+ $state.go("home",{}, {reload:'base'})
+ });
+ });
+ };
+
+ //TODO: missing unit tests
+ $rootScope.$on('OC:UpdatePromotions', function(event, orderid) {
+ OrderCloud.Orders.ListPromotions(orderid)
+ .then(function(data) {
+ if (data.Meta) {
+ vm.promotions = data.Items;
+ } else {
+ vm.promotions = data;
+ }
+ });
+ });
+}
diff --git a/src/app/cart/cart.md b/src/app/cart/cart.md
new file mode 100644
index 00000000..32bcfe3a
--- /dev/null
+++ b/src/app/cart/cart.md
@@ -0,0 +1,7 @@
+## Cart Component Overview
+
+This component allows buyer users with an open order to view the items on the order.
+
+It uses the Orders, and LineItems resources to build up the data.
+
+Cart is a buyer perspective component, and can only be accessed when logged in as a buyer user, or when impersonating a buyer user.
diff --git a/src/app/cart/templates/cart.tpl.html b/src/app/cart/templates/cart.tpl.html
new file mode 100644
index 00000000..f35a10b4
--- /dev/null
+++ b/src/app/cart/templates/cart.tpl.html
@@ -0,0 +1,116 @@
+
+
+
+ Cancel Order
+
+ Continue Shopping
+
+ Shopping Cart
+
+
+ You do not have any items in your cart.
+
+
+
+
+
+ Order Summary
+
+
+
+
+
+
+
Subtotal: {{base.currentOrder.Subtotal | currency}}
+
+
+
+ {{promotion.Code}}
+ remove
+ - {{promotion.Amount | currency}}
+
+
+
+
+
+
+
+ Proceed to Checkout
+
+
\ No newline at end of file
diff --git a/src/app/cart/templates/minicart.tpl.html b/src/app/cart/templates/minicart.tpl.html
new file mode 100644
index 00000000..cf81cf38
--- /dev/null
+++ b/src/app/cart/templates/minicart.tpl.html
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{minicart.LineItems.Meta.TotalCount}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{lineItem.Quantity}}
+
+ {{lineItem.Product.Name}}
+ {{lineItem.ProductID}}
+
+
+
+ {{lineItem.LineTotal | currency}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/cart/templates/modalMinicart.tpl.html b/src/app/cart/templates/modalMinicart.tpl.html
new file mode 100644
index 00000000..6a992b3d
--- /dev/null
+++ b/src/app/cart/templates/modalMinicart.tpl.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ {{lineItem.Quantity}}
+
+ {{lineItem.Product.Name}}
+ {{lineItem.ProductID}}
+
+
+
+ {{lineItem.LineTotal | currency}}
+
+
+
+
+
+
+
+
diff --git a/src/app/cart/test/cart.spec.js b/src/app/cart/test/cart.spec.js
new file mode 100644
index 00000000..cd29583f
--- /dev/null
+++ b/src/app/cart/test/cart.spec.js
@@ -0,0 +1,127 @@
+describe('Component: Cart', function() {
+ var scope,
+ q,
+ oc,
+ currentOrder,
+ rootScope,
+ lineItemsList,
+ _ocLineItems,
+ fakeOrder,
+ user
+ ;
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(module(function($provide) {
+ $provide.value('CurrentOrder', {ID: "MockOrderID3456"});
+ }));
+ beforeEach(inject(function($rootScope, $q, OrderCloud, ocLineItems, CurrentOrder) {
+ scope = $rootScope.$new();
+ q = $q;
+ oc = OrderCloud;
+ _ocLineItems = ocLineItems;
+ currentOrder = CurrentOrder;
+ rootScope = $rootScope;
+ fakeOrder = {
+ ID: "TestOrder123456789",
+ Type: "Standard",
+ FromUserID: "TestUser123456789",
+ BillingAddressID: "TestAddress123456789",
+ ShippingAddressID: "TestAddress123456789",
+ SpendingAccountID: null,
+ Comments: null,
+ PaymentMethod: null,
+ CreditCardID: null,
+ ShippingCost: null,
+ TaxCost: null
+ };
+ lineItemsList = {
+ "Items" : [{ID:"LI1"}, {ID:"LI2"}],
+ "Meta" : {
+ "Page": 1,
+ "PageSize": 20,
+ "TotalCount":29,
+ "TotalPages": 3,
+ "ItemRange" : [1,2]
+ }
+ };
+ user = {
+ ID: "TestUser132456789",
+ xp: {
+ defaultShippingAddressID: "TestAddress123456789",
+ defaultBillingAddressID: "TestAddress123456789",
+ defaultCreditCardID: "creditCard"
+ }
+
+ };
+ }));
+
+ describe('State: Cart', function() {
+ var state;
+ beforeEach(inject(function($state) {
+ state = $state.get('cart');
+ var defer = q.defer();
+ defer.resolve(lineItemsList);
+ spyOn(oc.LineItems,'List').and.returnValue(defer.promise);
+ spyOn(_ocLineItems,'GetProductInfo').and.returnValue(defer.promise);
+
+ }));
+ it('should call LineItems.List',inject(function($injector){
+ $injector.invoke(state.resolve.LineItemsList);
+ expect(oc.LineItems.List).toHaveBeenCalledWith(currentOrder.ID);
+ }));
+ it('should call LineItemHelper', inject(function($injector){
+ $injector.invoke(state.resolve.LineItemsList);
+ scope.$digest();
+ expect(_ocLineItems.GetProductInfo).toHaveBeenCalled();
+ }));
+ });
+
+ describe('Controller : CartController',function() {
+ var cartController;
+ var confirm;
+ beforeEach(inject(function($state, $controller, ocConfirm) {
+ cartController = $controller('CartCtrl', {
+ $scope: scope,
+ CurrentPromotions: [],
+ LineItemsList: lineItemsList
+ });
+ confirm = ocConfirm;
+ var defer = q.defer();
+ defer.resolve(lineItemsList);
+ spyOn(rootScope, '$broadcast');
+ }));
+
+ describe('removeItem()', function() {
+ beforeEach(function() {
+ var df = q.defer();
+ df.resolve();
+ spyOn(oc.LineItems, 'Delete').and.returnValue(df.promise);
+ });
+ it ('should delete the line item', function() {
+ cartController.removeItem(fakeOrder, {$index:0, lineItem: lineItemsList.Items[0]});
+ expect(oc.LineItems.Delete).toHaveBeenCalledWith(fakeOrder.ID, lineItemsList.Items[0].ID);
+ scope.$digest();
+ expect(rootScope.$broadcast).toHaveBeenCalledWith('OC:UpdateOrder', fakeOrder.ID);
+ expect(cartController.lineItems.Items).toEqual([{ID:"LI2"}]);
+ })
+ });
+
+ describe('CancelOrder',function() {
+ beforeEach(function() {
+ var df = q.defer();
+ df.resolve();
+ spyOn(confirm,'Confirm').and.returnValue(df.promise);
+ spyOn(oc.Orders, 'Delete').and.returnValue(df.promise);
+ });
+ it('should call OrderCloud Confirm modal prompt', function() {
+ cartController.cancelOrder();
+ expect(confirm.Confirm).toHaveBeenCalled();
+ });
+ it('should call OC Orders Delete Method', function(){
+ cartController.cancelOrder(fakeOrder);
+ scope.$digest();
+ expect(oc.Orders.Delete).toHaveBeenCalledWith(fakeOrder.ID);
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/app/categoryBrowse/categoryBrowse.js b/src/app/categoryBrowse/categoryBrowse.js
new file mode 100644
index 00000000..b9f33471
--- /dev/null
+++ b/src/app/categoryBrowse/categoryBrowse.js
@@ -0,0 +1,75 @@
+angular.module('orderCloud')
+ .config(CategoryBrowseConfig)
+ .controller('CategoryBrowseCtrl', CategoryBrowseController)
+;
+
+function CategoryBrowseConfig($stateProvider, catalogid){
+ $stateProvider
+ .state('categoryBrowse', {
+ parent:'base',
+ url:'/browse/categories?categoryID?productPage?categoryPage?pageSize?sortBy?filters',
+ templateUrl:'categoryBrowse/templates/categoryBrowse.tpl.html',
+ controller:'CategoryBrowseCtrl',
+ controllerAs:'categoryBrowse',
+ resolve: {
+ Parameters: function($stateParams, ocParameters) {
+ return ocParameters.Get($stateParams);
+ },
+ CategoryList: function(OrderCloud, Parameters) {
+ if(Parameters.categoryID) { Parameters.filters ? Parameters.filters.ParentID = Parameters.categoryID : Parameters.filters = {ParentID:Parameters.categoryID}; }
+ return OrderCloud.Me.ListCategories(null, Parameters.categoryPage, Parameters.pageSize || 12, null, Parameters.sortBy, Parameters.filters, 1);
+ },
+ ProductList: function(OrderCloud, Parameters) {
+ if(Parameters && Parameters.filters && Parameters.filters.ParentID) {
+ delete Parameters.filters.ParentID;
+ return OrderCloud.Me.ListProducts(null, Parameters.productPage, Parameters.pageSize || 12, null, Parameters.sortBy, Parameters.filters, Parameters.categoryID);
+ } else {
+ return null;
+ }
+ },
+ SelectedCategory: function(OrderCloud, Parameters){
+ if(Parameters.categoryID){
+ return OrderCloud.Me.ListCategories(null, 1, 1, null, null, {ID:Parameters.categoryID}, 'all')
+ .then(function(data){
+ return data.Items[0];
+ });
+
+ } else {
+ return null;
+ }
+
+ }
+ }
+ });
+}
+
+function CategoryBrowseController($state, ocParameters, CategoryList, ProductList, Parameters, SelectedCategory) {
+ var vm = this;
+ vm.categoryList = CategoryList;
+ vm.productList = ProductList;
+ vm.parameters = Parameters;
+ vm.selectedCategory = SelectedCategory;
+
+ vm.getNumberOfResults = function(list){
+ return vm[list].Meta.ItemRange[0] + ' - ' + vm[list].Meta.ItemRange[1] + ' of ' + vm[list].Meta.TotalCount + ' results';
+ };
+
+ vm.filter = function(resetPage) {
+ $state.go('.', ocParameters.Create(vm.parameters, resetPage));
+ };
+
+ vm.updateCategoryList = function(category){
+ vm.parameters.categoryID = category;
+ vm.filter(true);
+ };
+
+ vm.changeCategoryPage = function(newPage){
+ vm.parameters.categoryPage = newPage;
+ vm.filter(false);
+ };
+
+ vm.changeProductPage = function(newPage){
+ vm.parameters.productPage = newPage;
+ vm.filter(false);
+ };
+}
diff --git a/src/app/categoryBrowse/templates/categoryBrowse.tpl.html b/src/app/categoryBrowse/templates/categoryBrowse.tpl.html
new file mode 100644
index 00000000..5ff76499
--- /dev/null
+++ b/src/app/categoryBrowse/templates/categoryBrowse.tpl.html
@@ -0,0 +1,56 @@
+
+
+
+
+
{{categoryBrowse.getNumberOfResults('categoryList')}}
+
+
+
+
+
+
{{category.Name || category.ID}}
+
{{category.ID}}
+
+
+
+
+
+
+
+
+
+
+
+
{{categoryBrowse.getNumberOfResults('productList')}}
+
+
+
+
+
+
+
+
+
+ No products or categories
+
+
+
\ No newline at end of file
diff --git a/src/app/categoryBrowse/tests/categoryBrowse.spec.js b/src/app/categoryBrowse/tests/categoryBrowse.spec.js
new file mode 100644
index 00000000..fa6fa214
--- /dev/null
+++ b/src/app/categoryBrowse/tests/categoryBrowse.spec.js
@@ -0,0 +1,114 @@
+describe('Component: Category Browse', function(){
+ var scope,
+ q,
+ oc,
+ state,
+ _ocParameters,
+ mockProductList,
+ categoryList
+ ;
+ beforeEach(module(function($provide) {
+ $provide.value('Parameters', {categoryPage: null, productPage: null, pageSize: null, sortBy: null, filters: null, categoryID:null});
+ }));
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(inject(function($rootScope, $q, OrderCloud, ocParameters, $state){
+ scope = $rootScope.$new();
+ q = $q;
+ oc = OrderCloud;
+ state = $state;
+ _ocParameters = ocParameters;
+ categoryList = ['category1', 'category2'];
+ mockProductList = {
+ Items:['product1', 'product2'],
+ Meta:{
+ ItemRange:[1, 3],
+ TotalCount: 50
+ }
+ };
+ }));
+
+ describe('State: categoryBrowse', function(){
+ var state;
+ beforeEach(inject(function($state){
+ state = $state.get('categoryBrowse');
+ var defer = q.defer();
+ defer.resolve();
+ spyOn(_ocParameters, 'Get');
+ spyOn(oc.Me, 'ListCategories');
+ spyOn(oc.Me, 'ListProducts');
+ }));
+ it('should resolve Parameters', inject(function($injector){
+ $injector.invoke(state.resolve.Parameters);
+ expect(_ocParameters.Get).toHaveBeenCalled();
+ }));
+ it('should resolve CategoryList', inject(function($injector){
+ $injector.invoke(state.resolve.CategoryList);
+ expect(oc.Me.ListCategories).toHaveBeenCalled();
+ }));
+ it('CategoryList resolve should return subcategories of categoryID', inject(function($injector, Parameters){
+ Parameters.categoryID = '12';
+ $injector.invoke(state.resolve.CategoryList);
+ expect(oc.Me.ListCategories).toHaveBeenCalledWith(null, null, 12, null, null, {ParentID:'12'}, 1);
+ }));
+ it('should resolve ProductList', inject(function($injector, Parameters){
+ Parameters.filters = {ParentID:'12'};
+ $injector.invoke(state.resolve.ProductList);
+ expect(oc.Me.ListProducts).toHaveBeenCalled();
+ }));
+ it('ProductList should not return products when there is no ParentID filter', inject(function($injector){
+ //we don't want to return products on the top category level
+ $injector.invoke(state.resolve.ProductList);
+ expect(oc.Me.ListProducts).not.toHaveBeenCalled();
+ }));
+ });
+
+ describe('Controller: CategoryBrowseController', function(){
+ var categoryBrowseCtrl;
+ beforeEach(inject(function($state, $controller, Parameters){
+ var state = $state;
+ var selectedCategory = 'category1';
+ categoryBrowseCtrl = $controller('CategoryBrowseCtrl', {
+ $scope: scope,
+ $state: state,
+ ocParameters: _ocParameters,
+ CategoryList: categoryList,
+ ProductList: mockProductList,
+ Parameters: Parameters,
+ SelectedCategory: selectedCategory
+ });
+ spyOn(state, 'go');
+ spyOn(_ocParameters, 'Create');
+ }));
+ describe('filter', function(){
+ it('should reload state and call ocParameters.Create with any parameters', function(){
+ categoryBrowseCtrl.parameters = {pageSize: 1};
+ categoryBrowseCtrl.filter(true);
+ expect(state.go).toHaveBeenCalled();
+ expect(_ocParameters.Create).toHaveBeenCalledWith({pageSize:1}, true);
+ });
+ });
+ describe('updateCategoryList', function(){
+ it('should reload state with new category ID parameter', function(){
+ categoryBrowseCtrl.updateCategoryList('newCategoryID');
+ expect(state.go).toHaveBeenCalled();
+ expect(_ocParameters.Create).toHaveBeenCalledWith({categoryPage: null, productPage: null, pageSize: null, sortBy: null, filters: null, categoryID:'newCategoryID'}, true);
+ });
+ });
+ describe('changeCategoryPage', function(){
+ it('should reload state with the new categoryPage', function(){
+ categoryBrowseCtrl.changeCategoryPage('newCategoryPage');
+ expect(state.go).toHaveBeenCalled();
+ expect(_ocParameters.Create).toHaveBeenCalledWith({categoryPage: 'newCategoryPage', productPage: null, pageSize: null, sortBy: null, filters: null, categoryID: null}, false);
+ });
+ });
+ describe('changeProductPage', function(){
+ it('should reload state with the new productPage', function(){
+ categoryBrowseCtrl.changeProductPage('newProductPage', function(){
+ expect(state.go).toHaveBeenCalled();
+ expect(_ocParameters.Create).toHaveBeenCalledWith({categoryPage: null, productPage: 'newProductPage', pageSize: null, sortBy: null, filters: null, categoryID: null}, false);
+ });
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/app/checkout/checkout.js b/src/app/checkout/checkout.js
new file mode 100644
index 00000000..f9f9988c
--- /dev/null
+++ b/src/app/checkout/checkout.js
@@ -0,0 +1,162 @@
+angular.module('orderCloud')
+ .config(checkoutConfig)
+ .controller('CheckoutCtrl', CheckoutController)
+ .factory('AddressSelectModal', AddressSelectModalService)
+ .controller('AddressSelectCtrl', AddressSelectController)
+ .constant('CheckoutConfig', {
+ ShippingRates: true,
+ TaxRates: false,
+ AvailablePaymentMethods: ['PurchaseOrder', 'CreditCard', 'SpendingAccount']
+ })
+;
+
+function checkoutConfig($urlRouterProvider, $stateProvider) {
+ $urlRouterProvider.when('/checkout', '/checkout/shipping');
+ $stateProvider
+ .state('checkout', {
+ abstract:true,
+ parent: 'base',
+ url: '/checkout',
+ templateUrl: 'checkout/templates/checkout.tpl.html',
+ controller: 'CheckoutCtrl',
+ controllerAs: 'checkout',
+ resolve: {
+ OrderShipAddress: function($q, OrderCloud, CurrentOrder){
+ var deferred = $q.defer();
+ if (CurrentOrder.ShippingAddressID) {
+ OrderCloud.Me.GetAddress(CurrentOrder.ShippingAddressID)
+ .then(function(address) {
+ deferred.resolve(address);
+ })
+ .catch(function(ex) {
+ deferred.resolve(null);
+ });
+ }
+ else {
+ deferred.resolve(null);
+ }
+
+ return deferred.promise;
+ },
+ CurrentPromotions: function(CurrentOrder, OrderCloud) {
+ return OrderCloud.Orders.ListPromotions(CurrentOrder.ID);
+ },
+ OrderBillingAddress: function($q, OrderCloud, CurrentOrder){
+ var deferred = $q.defer();
+
+ if (CurrentOrder.BillingAddressID) {
+ OrderCloud.Me.GetAddress(CurrentOrder.BillingAddressID)
+ .then(function(address) {
+ deferred.resolve(address);
+ })
+ .catch(function(ex) {
+ deferred.resolve(null);
+ });
+ }
+ else {
+ deferred.resolve(null);
+ }
+ return deferred.promise;
+ }
+ }
+ })
+ ;
+}
+
+function CheckoutController($state, $rootScope, toastr, OrderCloud, OrderShipAddress, CurrentPromotions, OrderBillingAddress, CheckoutConfig) {
+ var vm = this;
+ vm.shippingAddress = OrderShipAddress;
+ vm.billingAddress = OrderBillingAddress;
+ vm.promotions = CurrentPromotions.Items;
+ vm.checkoutConfig = CheckoutConfig;
+
+ vm.submitOrder = function(order) {
+ OrderCloud.Orders.Submit(order.ID)
+ .then(function(order) {
+ $state.go('confirmation', {orderid:order.ID}, {reload:'base'});
+ toastr.success('Your order has been submitted', 'Success');
+ })
+ .catch(function(ex) {
+ toastr.error('Your order did not submit successfully.', 'Error');
+ });
+ };
+
+ $rootScope.$on('OC:OrderShipAddressUpdated', function(event, order) {
+ OrderCloud.Me.GetAddress(order.ShippingAddressID)
+ .then(function(address){
+ vm.shippingAddress = address;
+ });
+ });
+
+ $rootScope.$on('OC:OrderBillAddressUpdated', function(event, order){
+ OrderCloud.Me.GetAddress(order.BillingAddressID)
+ .then(function(address){
+ vm.billingAddress = address;
+ });
+ });
+
+ vm.removePromotion = function(order, promotion) {
+ OrderCloud.Orders.RemovePromotion(order.ID, promotion.Code)
+ .then(function() {
+ $rootScope.$broadcast('OC:UpdatePromotions', order.ID);
+ })
+ };
+
+ $rootScope.$on('OC:UpdatePromotions', function(event, orderid) {
+ OrderCloud.Orders.ListPromotions(orderid)
+ .then(function(data) {
+ if (data.Meta) {
+ vm.promotions = data.Items;
+ } else {
+ vm.promotions = data;
+ }
+ $rootScope.$broadcast('OC:UpdateOrder', orderid);
+ })
+ });
+}
+
+function AddressSelectModalService($uibModal) {
+ var service = {
+ Open: _open
+ };
+
+ function _open(type) {
+ return $uibModal.open({
+ templateUrl: 'checkout/templates/addressSelect.modal.tpl.html',
+ controller: 'AddressSelectCtrl',
+ controllerAs: 'addressSelect',
+ backdrop: 'static',
+ size: 'md',
+ resolve: {
+ Addresses: function(OrderCloud) {
+ if (type == 'shipping') {
+ return OrderCloud.Me.ListAddresses(null, 1, 100, null, null, {Shipping: true});
+ } else if (type == 'billing') {
+ return OrderCloud.Me.ListAddresses(null, 1, 100, null, null, {Billing: true});
+ } else {
+ return OrderCloud.Me.ListAddresses(null, 1, 100);
+ }
+ }
+ }
+ }).result;
+ }
+
+ return service;
+}
+
+function AddressSelectController($uibModalInstance, Addresses) {
+ var vm = this;
+ vm.addresses = Addresses;
+
+ vm.select = function (address) {
+ $uibModalInstance.close(address);
+ };
+
+ vm.createAddress = function() {
+ $uibModalInstance.close('create');
+ };
+
+ vm.cancel = function () {
+ $uibModalInstance.dismiss();
+ };
+}
\ No newline at end of file
diff --git a/src/app/checkout/checkout.md b/src/app/checkout/checkout.md
new file mode 100644
index 00000000..885c705d
--- /dev/null
+++ b/src/app/checkout/checkout.md
@@ -0,0 +1,7 @@
+## Checkout Component Overview
+
+This component allows buyer users with an open order to submit their order with shipping, billing, and payment information.
+
+It uses the Orders, and LineItems resources to build up the data.
+
+Checkout is a buyer perspective component, and can only be accessed when logged in as a buyer user, or when impersonating a buyer user.
diff --git a/src/app/checkout/confirmation/checkout.confirmation.js b/src/app/checkout/confirmation/checkout.confirmation.js
new file mode 100644
index 00000000..4c056695
--- /dev/null
+++ b/src/app/checkout/confirmation/checkout.confirmation.js
@@ -0,0 +1,85 @@
+angular.module('orderCloud')
+ .config(checkoutConfirmationConfig)
+ .controller('CheckoutConfirmationCtrl', CheckoutConfirmationController);
+
+function checkoutConfirmationConfig($stateProvider) {
+ $stateProvider
+ .state('confirmation', {
+ parent: 'base',
+ url: '/confirmation/:orderid',
+ templateUrl: 'checkout/confirmation/templates/checkout.confirmation.tpl.html',
+ controller: 'CheckoutConfirmationCtrl',
+ controllerAs: 'checkoutConfirmation',
+ resolve: {
+ SubmittedOrder: function($stateParams, OrderCloud) {
+ return OrderCloud.Me.GetOrder($stateParams.orderid);
+ },
+ OrderShipAddress: function(SubmittedOrder, OrderCloud){
+ return OrderCloud.Me.GetAddress(SubmittedOrder.ShippingAddressID);
+ },
+ OrderPromotions: function(SubmittedOrder, OrderCloud) {
+ return OrderCloud.Orders.ListPromotions(SubmittedOrder.ID);
+ },
+ OrderBillingAddress: function(SubmittedOrder, OrderCloud){
+ return OrderCloud.Me.GetAddress(SubmittedOrder.BillingAddressID);
+ },
+ OrderPayments: function($q, SubmittedOrder, OrderCloud) {
+ var deferred = $q.defer();
+ OrderCloud.Payments.List(SubmittedOrder.ID)
+ .then(function(data) {
+ var queue = [];
+ angular.forEach(data.Items, function(payment, index) {
+ if (payment.Type === 'CreditCard' && payment.CreditCardID) {
+ queue.push((function() {
+ var d = $q.defer();
+ OrderCloud.Me.GetCreditCard(payment.CreditCardID)
+ .then(function(cc) {
+ data.Items[index].Details = cc;
+ d.resolve();
+ });
+ return d.promise;
+ })());
+ } else if (payment.Type === 'SpendingAccount' && payment.SpendingAccountID) {
+ queue.push((function() {
+ var d = $q.defer();
+ OrderCloud.Me.GetSpendingAccount(payment.SpendingAccountID)
+ .then(function(cc) {
+ data.Items[index].Details = cc;
+ d.resolve();
+ });
+ return d.promise;
+ })());
+ }
+ });
+ $q.all(queue)
+ .then(function() {
+ deferred.resolve(data);
+ })
+ });
+
+ return deferred.promise;
+ },
+ LineItemsList: function($q, $state, toastr, ocLineItems, SubmittedOrder, OrderCloud) {
+ var dfd = $q.defer();
+ OrderCloud.LineItems.List(SubmittedOrder.ID)
+ .then(function(data) {
+ ocLineItems.GetProductInfo(data.Items)
+ .then(function() {
+ dfd.resolve(data);
+ });
+ });
+ return dfd.promise;
+ }
+ }
+ });
+}
+
+function CheckoutConfirmationController(SubmittedOrder, OrderShipAddress, OrderPromotions, OrderBillingAddress, OrderPayments, LineItemsList) {
+ var vm = this;
+ vm.order = SubmittedOrder;
+ vm.shippingAddress = OrderShipAddress;
+ vm.promotions = OrderPromotions.Items;
+ vm.billingAddress = OrderBillingAddress;
+ vm.payments = OrderPayments.Items;
+ vm.lineItems = LineItemsList;
+}
\ No newline at end of file
diff --git a/src/app/checkout/confirmation/templates/checkout.confirmation.tpl.html b/src/app/checkout/confirmation/templates/checkout.confirmation.tpl.html
new file mode 100644
index 00000000..da1befe8
--- /dev/null
+++ b/src/app/checkout/confirmation/templates/checkout.confirmation.tpl.html
@@ -0,0 +1,138 @@
+
+
+
+
+
Order ID: {{checkoutConfirmation.order.ID}}
+
Date Submitted: {{checkoutConfirmation.order.DateSubmitted | date:'short'}}
+
Subtotal: {{checkoutConfirmation.order.Subtotal | currency}}
+
Shipping: + {{checkoutConfirmation.order.ShippingCost | currency}}
+
+ {{promotion.Code}}
+ - {{promotion.Amount | currency}}
+
+
Total: {{checkoutConfirmation.order.Total | currency}}
+
+
+
+
+
+
+
{{payment.Type | humanize}} {{payment.Amount | currency}}
+
+
PO#: {{payment.xp.PONumber}}
+
+
+
+ XXXX-XXXX-XXXX- {{payment.Details.PartialAccountNumber}}
+
+
+
+ {{payment.Details.Name}}
+ Remaining Balance: {{payment.Details.Balance | currency}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{lineItem.ProductID}}
+
+
+ {{spec.Name}}:
+ {{spec.Value}}
+
+
+
+
+
+
+
{{lineItem.UnitPrice | currency}}
+
+
+
+ {{lineItem.Quantity}}
+
+
+ {{'x ' + lineItem.Product.QuantityMultiplier + (lineItem.Quantity ? (' (' + (lineItem.Quantity * lineItem.Product.QuantityMultiplier) + ')') : '')}}
+
+
+
+
{{lineItem.LineTotal | currency}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/checkout/payment/checkout.payment.js b/src/app/checkout/payment/checkout.payment.js
new file mode 100644
index 00000000..e7ea2ba0
--- /dev/null
+++ b/src/app/checkout/payment/checkout.payment.js
@@ -0,0 +1,88 @@
+angular.module('orderCloud')
+ .config(checkoutPaymentConfig)
+ .controller('CheckoutPaymentCtrl', CheckoutPaymentController)
+ .factory('CheckoutPaymentService', CheckoutPaymentService)
+;
+
+function checkoutPaymentConfig($stateProvider) {
+ $stateProvider
+ .state('checkout.payment', {
+ url: '/payment',
+ templateUrl: 'checkout/payment/templates/checkout.payment.tpl.html',
+ controller: 'CheckoutPaymentCtrl',
+ controllerAs: 'checkoutPayment'
+ })
+ ;
+}
+
+function CheckoutPaymentController($exceptionHandler, $rootScope, toastr, OrderCloud, AddressSelectModal, MyAddressesModal) {
+ var vm = this;
+ vm.createAddress = createAddress;
+ vm.changeBillingAddress = changeBillingAddress;
+
+ function createAddress(order){
+ return MyAddressesModal.Create()
+ .then(function(address) {
+ toastr.success('Address Created', 'Success');
+ order.BillingAddressID = address.ID;
+ saveBillingAddress(order);
+ });
+ }
+
+ function changeBillingAddress(order) {
+ AddressSelectModal.Open('billing')
+ .then(function(address) {
+ if (address == 'create') {
+ createAddress(order);
+ } else {
+ order.BillingAddressID = address.ID;
+ saveBillingAddress(order);
+ }
+ });
+ }
+
+ function saveBillingAddress(order) {
+ if (order && order.BillingAddressID) {
+ OrderCloud.Orders.Patch(order.ID, {BillingAddressID: order.BillingAddressID})
+ .then(function(updatedOrder) {
+ $rootScope.$broadcast('OC:OrderBillAddressUpdated', updatedOrder);
+ })
+ .catch(function(ex) {
+ $exceptionHandler(ex);
+ });
+ }
+ }
+}
+
+function CheckoutPaymentService($q, OrderCloud) {
+ var service = {
+ PaymentsExceedTotal: _paymentsExceedTotal,
+ RemoveAllPayments: _removeAllPayments
+ };
+
+ function _paymentsExceedTotal(payments, orderTotal) {
+ var paymentTotal = 0;
+ angular.forEach(payments.Items, function(payment) {
+ paymentTotal += payment.Amount;
+ });
+
+ return paymentTotal.toFixed(2) > orderTotal;
+ }
+
+ function _removeAllPayments(payments, order) {
+ var deferred = $q.defer();
+
+ var queue = [];
+ angular.forEach(payments.Items, function(payment) {
+ queue.push(OrderCloud.Payments.Delete(order.ID, payment.ID));
+ });
+
+ $q.all(queue).then(function() {
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ }
+
+ return service;
+}
diff --git a/src/app/checkout/payment/directives/README.md b/src/app/checkout/payment/directives/README.md
new file mode 100644
index 00000000..0e316ca8
--- /dev/null
+++ b/src/app/checkout/payment/directives/README.md
@@ -0,0 +1,28 @@
+#Payment Directives
+
+**All 5 directives accept a global `order` option for passing in `base.currentOrder`**
+
+**oc-payment-po** => single payment, single type of purchase order
+- _options:_
+ - `payment`: the payment to be tied to the directive functionality (for multiple payments)
+
+**oc-payment-sa** => single payment, single type of spending account
+- _options:_
+ - `excluded-options`: an array of spending account IDs to be excluded
+ - `payment`: the payment to be tied to the directive functionality (for multiple payments)
+
+**oc-payment-cc** => single payment, single type of credit card
+- _options:_
+ - `excluded-options`: an array of credit card IDs to be excluded
+ - `payment`: the payment to be tied to the directive functionality (for multiple payments)
+
+**oc-payment** => single payment, multiple types (configurable)
+- _options:_
+ - `excluded-options`: an object with a property for `SpendingAccounts` & `CreditCards` which are an array of IDs to be excluded
+ - `payment-index`: the $index of the payment when using multiple payments, payment
+ - `payment`: the payment to be tied to the directive functionality (for multiple payments)
+ - `methods`: an array of methods to be made available in the payment method dropdown
+
+**oc-payments** => multiple payments, multiple types (configurable)
+- _options_
+ - `methods`: an array of methods to be made available in the payment method dropdown
\ No newline at end of file
diff --git a/src/app/checkout/payment/directives/payment.directives.js b/src/app/checkout/payment/directives/payment.directives.js
new file mode 100644
index 00000000..aac75bcf
--- /dev/null
+++ b/src/app/checkout/payment/directives/payment.directives.js
@@ -0,0 +1,377 @@
+angular.module('orderCloud')
+
+ //Single Purchase Order Payment
+ .directive('ocPaymentPo', OCPaymentPurchaseOrder)
+ .controller('PaymentPurchaseOrderCtrl', PaymentPurchaseOrderController)
+
+ //Single Spending Account Payment
+ .directive('ocPaymentSa', OCPaymentSpendingAccount)
+ .controller('PaymentSpendingAccountCtrl', PaymentSpendingAccountController)
+
+ //Single Credit Card Payment
+ .directive('ocPaymentCc', OCPaymentCreditCard)
+ .controller('PaymentCreditCardCtrl', PaymentCreditCardController)
+
+ //Single Payment, Multiple Types
+ .directive('ocPayment', OCPayment)
+ .controller('PaymentCtrl', PaymentController)
+
+ //Multiple Payment, Multiple Types
+ .directive('ocPayments', OCPayments)
+ .controller('PaymentsCtrl', PaymentsController)
+;
+
+
+function OCPaymentPurchaseOrder() {
+ return {
+ restrict:'E',
+ scope: {
+ order: '=',
+ payment: '=?'
+ },
+ templateUrl: 'checkout/payment/directives/templates/purchaseOrder.tpl.html',
+ controller: 'PaymentPurchaseOrderCtrl'
+ }
+}
+
+function PaymentPurchaseOrderController($scope, $rootScope, toastr, OrderCloud, $exceptionHandler) {
+ if (!$scope.payment) {
+ OrderCloud.Payments.List($scope.order.ID)
+ .then(function(data) {
+ if (data.Items.length) {
+ OrderCloud.Payments.Patch($scope.order.ID, data.Items[0].ID, {
+ Type: 'PurchaseOrder',
+ CreditCardID: null,
+ SpendingAccountID: null,
+ Amount: null
+ }).then(function(data) {
+ $scope.payment = data;
+ });
+ } else {
+ OrderCloud.Payments.Create($scope.order.ID, {Type: 'PurchaseOrder'})
+ .then(function(data) {
+ $scope.payment = data;
+ });
+ }
+ });
+ } else if (!($scope.payment.Type == "PurchaseOrder" && $scope.payment.CreditCardID == null && $scope.payment.SpendingAccountID == null)) {
+ $scope.payment.Type = "PurchaseOrder";
+ $scope.payment.CreditCardID = null;
+ $scope.payment.SpendingAccountID = null;
+ OrderCloud.Payments.Patch($scope.order.ID, $scope.payment.ID, $scope.payment).then(function() {
+ toastr.success('Paying by purchase order', 'Purchase Order Payment');
+ $rootScope.$broadcast('OC:PaymentsUpdated');
+ });
+ }
+
+ $scope.updatePayment = function() {
+ if ($scope.payment.xp && $scope.payment.xp.PONumber && (!$scope.payment.xp.PONumber.length)) $scope.payment.xp.PONumber = null;
+ OrderCloud.Payments.Update($scope.order.ID, $scope.payment.ID, $scope.payment)
+ .then(function() {
+ toastr.success('Purchase Order Number Saved');
+ $rootScope.$broadcast('OC:PaymentsUpdated');
+ })
+ .catch(function(ex) {
+ $exceptionHandler(ex);
+ });
+ }
+}
+
+function OCPaymentSpendingAccount() {
+ return {
+ restrict:'E',
+ scope: {
+ order: '=',
+ payment: '=?',
+ excludedSpendingAccounts: '=?excludeOptions'
+ },
+ templateUrl: 'checkout/payment/directives/templates/spendingAccount.tpl.html',
+ controller: 'PaymentSpendingAccountCtrl',
+ controllerAs: 'paymentSA'
+ }
+}
+
+function PaymentSpendingAccountController($scope, $rootScope, toastr, OrderCloud, $exceptionHandler) {
+ OrderCloud.Me.ListSpendingAccounts(null, 1, 100, null, null, {RedemptionCode: '!*', AllowAsPaymentMethod: true})
+ .then(function(data) {
+ $scope.spendingAccounts = data.Items;
+ });
+
+ if (!$scope.payment) {
+ OrderCloud.Payments.List($scope.order.ID)
+ .then(function(data) {
+ if (data.Items.length) {
+ OrderCloud.Payments.Patch($scope.order.ID, data.Items[0].ID, {
+ Type: 'SpendingAccount',
+ xp: {
+ PONumber:null
+ },
+ CreditCardID:null,
+ SpendingAccountID:null,
+ Amount:null
+ }).then(function(data) {
+ $scope.payment = data;
+ if (!$scope.payment.SpendingAccountID) $scope.showPaymentOptions = true;
+ });
+ } else {
+ OrderCloud.Payments.Create($scope.order.ID, {Type: 'SpendingAccount'})
+ .then(function(data) {
+ $scope.payment = data;
+ $scope.showPaymentOptions = true;
+ });
+ }
+ });
+ } else {
+ delete $scope.payment.CreditCardID;
+ if ($scope.payment.xp && $scope.payment.xp.PONumber) $scope.payment.xp.PONumber = null;
+ if (!$scope.payment.SpendingAccountID) $scope.showPaymentOptions = true;
+ }
+
+ $scope.changePayment = function() {
+ $scope.showPaymentOptions = true;
+ };
+
+ $scope.updatePayment = function(scope) {
+ var oldSelection = angular.copy($scope.payment.SpendingAccountID);
+ $scope.payment.SpendingAccountID = scope.spendingAccount.ID;
+ $scope.updatingSpendingAccountPayment = OrderCloud.Payments.Update($scope.order.ID, $scope.payment.ID, $scope.payment)
+ .then(function() {
+ $scope.showPaymentOptions = false;
+ toastr.success('Using ' + scope.spendingAccount.Name,'Spending Account Payment');
+ $rootScope.$broadcast('OC:PaymentsUpdated');
+ })
+ .catch(function(ex) {
+ $scope.payment.SpendingAccountID = oldSelection;
+ $exceptionHandler(ex);
+ });
+ };
+
+ $scope.$watch('payment', function(n,o) {
+ if (n && !n.SpendingAccountID) {
+ $scope.OCPaymentSpendingAccount.$setValidity('SpendingAccount_Not_Set', false);
+ } else {
+ $scope.OCPaymentSpendingAccount.$setValidity('SpendingAccount_Not_Set', true);
+ }
+ }, true);
+}
+
+function OCPaymentCreditCard() {
+ return {
+ restrict:'E',
+ scope: {
+ order: '=',
+ payment: '=?',
+ excludedCreditCards: '=?excludeOptions'
+ },
+ templateUrl: 'checkout/payment/directives/templates/creditCard.tpl.html',
+ controller: 'PaymentCreditCardCtrl',
+ controllerAs: 'paymentCC'
+ }
+}
+
+function PaymentCreditCardController($scope, $rootScope, toastr, $filter, OrderCloud, MyPaymentCreditCardModal, $exceptionHandler) {
+ OrderCloud.Me.ListCreditCards(null, 1, 100, null, null, {})
+ .then(function(data) {
+ $scope.creditCards = data.Items;
+ });
+
+ if (!$scope.payment) {
+ OrderCloud.Payments.List($scope.order.ID)
+ .then(function(data) {
+ if (data.Items.length) {
+ OrderCloud.Payments.Patch($scope.order.ID, data.Items[0].ID, {
+ Type: 'CreditCard',
+ xp: {
+ PONumber: null
+ },
+ SpendingAccountID: null,
+ Amount: null
+ }).then(function(data) {
+ $scope.payment = data;
+ if (!$scope.payment.SpendingAccountID) $scope.showPaymentOptions = true;
+ });
+ } else {
+ OrderCloud.Payments.Create($scope.order.ID, {Type: 'CreditCard'})
+ .then(function(data) {
+ $scope.payment = data;
+ $scope.showPaymentOptions = true;
+ });
+ }
+ });
+ } else {
+ delete $scope.payment.SpendingAccountID;
+ if ($scope.payment.xp && $scope.payment.xp.PONumber) $scope.payment.xp.PONumber = null;
+ if (!$scope.payment.CreditCardID) $scope.showPaymentOptions = true;
+ }
+
+ $scope.changePayment = function() {
+ $scope.showPaymentOptions = true;
+ };
+
+ $scope.$watch('payment', function(n,o) {
+ if (n && !n.CreditCardID) {
+ $scope.OCPaymentCreditCard.$setValidity('CreditCard_Not_Set', false);
+ } else {
+ $scope.OCPaymentCreditCard.$setValidity('CreditCard_Not_Set', true);
+
+ }
+ }, true);
+
+ $scope.updatePayment = function(scope) {
+ var oldSelection = angular.copy($scope.payment.CreditCardID);
+ $scope.payment.CreditCardID = scope.creditCard.ID;
+ $scope.updatingCreditCardPayment = OrderCloud.Payments.Update($scope.order.ID, $scope.payment.ID, $scope.payment)
+ .then(function() {
+ $scope.showPaymentOptions = false;
+ toastr.success('Using ' + $filter('humanize')(scope.creditCard.CardType) + ' ending in ' + scope.creditCard.PartialAccountNumber,'Credit Card Payment');
+ $rootScope.$broadcast('OC:PaymentsUpdated');
+ })
+ .catch(function(ex) {
+ $scope.payment.CreditCardID = oldSelection;
+ $exceptionHandler(ex);
+ });
+ };
+
+ $scope.createCreditCard = function() {
+ MyPaymentCreditCardModal.Create()
+ .then(function(card) {
+ toastr.success('Credit Card Created', 'Success');
+ $scope.creditCards.push(card);
+ $scope.updatePayment({creditCard:card});
+ });
+ };
+}
+
+function OCPayment() {
+ return {
+ restrict:'E',
+ scope: {
+ order: '=',
+ methods: '=?',
+ payment: '=?',
+ paymentIndex: '=?',
+ excludeOptions: '=?'
+ },
+ templateUrl: 'checkout/payment/directives/templates/payment.tpl.html',
+ controller: 'PaymentCtrl',
+ controllerAs: 'ocPayment'
+ }
+}
+
+function PaymentController($scope, $rootScope, OrderCloud, CheckoutConfig) {
+ if (!$scope.methods) $scope.methods = CheckoutConfig.AvailablePaymentMethods;
+ if (!$scope.payment) {
+ OrderCloud.Payments.List($scope.order.ID)
+ .then(function(data) {
+ if (CheckoutPaymentService.PaymentsExceedTotal(data, $scope.order.Total)) {
+ CheckoutPaymentService.RemoveAllPayments(data, $scope.order)
+ .then(function(data) {
+ OrderCloud.Payments.Create($scope.order.ID, {Type: CheckoutConfig.AvailablePaymentMethods[0]})
+ .then(function(data) {
+ $scope.payment = data;
+ $rootScope.$broadcast('OC:PaymentsUpdated');
+ });
+ });
+ }
+ else if (data.Items.length) {
+ $scope.payment = data.Items[0];
+ if ($scope.methods.length == 1) $scope.payment.Type = $scope.methods[0];
+ } else {
+ OrderCloud.Payments.Create($scope.order.ID, {Type: CheckoutConfig.AvailablePaymentMethods[0]})
+ .then(function(data) {
+ $scope.payment = data;
+ $rootScope.$broadcast('OC:PaymentsUpdated');
+ });
+ }
+ });
+ } else if ($scope.methods.length == 1) {
+ $scope.payment.Type = $scope.methods[0];
+ }
+}
+
+function OCPayments() {
+ return {
+ restrict:'E',
+ scope: {
+ order: '=',
+ methods: '=?'
+ },
+ templateUrl: 'checkout/payment/directives/templates/payments.tpl.html',
+ controller: 'PaymentsCtrl'
+ }
+}
+
+function PaymentsController($rootScope, $scope, $exceptionHandler, toastr, OrderCloud, CheckoutPaymentService, CheckoutConfig) {
+ if (!$scope.methods) $scope.methods = CheckoutConfig.AvailablePaymentMethods;
+
+ OrderCloud.Payments.List($scope.order.ID)
+ .then(function(data) {
+ if (!data.Items.length) {
+ $scope.payments = {Items: []};
+ $scope.addNewPayment();
+ }
+ else if (CheckoutPaymentService.PaymentsExceedTotal(data, $scope.order.Total)) {
+ CheckoutPaymentService.RemoveAllPayments(data, $scope.order)
+ .then(function(data) {
+ $scope.payments = {Items: []};
+ $scope.addNewPayment();
+ });
+ }
+ else {
+ $scope.payments = data;
+ calculateMaxTotal();
+ }
+ });
+
+ $scope.addNewPayment = function() {
+ OrderCloud.Payments.Create($scope.order.ID, {Type: CheckoutConfig.AvailablePaymentMethods[0]})
+ .then(function(data) {
+ $scope.payments.Items.push(data);
+ calculateMaxTotal();
+ toastr.success('Payment Added');
+ });
+ };
+
+ $scope.removePayment = function(scope) {
+ OrderCloud.Payments.Delete($scope.order.ID, scope.payment.ID)
+ .then(function() {
+ $scope.payments.Items.splice(scope.$index, 1);
+ calculateMaxTotal();
+ toastr.success('Payment Removed');
+ });
+ };
+
+ $scope.updatePaymentAmount = function(scope) {
+ if (scope.payment.Amount > scope.payment.MaxAmount || !scope.payment.Amount) return;
+ OrderCloud.Payments.Update($scope.order.ID, scope.payment.ID, scope.payment)
+ .then(function(data) {
+ toastr.success('Payment Amount Updated');
+ calculateMaxTotal();
+ })
+ .catch(function(ex) {
+ $exceptionHandler(ex);
+ });
+ };
+
+ $rootScope.$on('OC:PaymentsUpdated', function() {
+ calculateMaxTotal();
+ });
+
+
+ function calculateMaxTotal() {
+ var paymentTotal = 0;
+ $scope.excludeOptions = {
+ SpendingAccounts: [],
+ CreditCards: []
+ };
+ angular.forEach($scope.payments.Items, function(payment) {
+ paymentTotal += payment.Amount;
+ if (payment.SpendingAccountID) $scope.excludeOptions.SpendingAccounts.push(payment.SpendingAccountID);
+ if (payment.CreditCardID) $scope.excludeOptions.CreditCards.push(payment.CreditCardID);
+ var maxAmount = $scope.order.Total - _.reduce(_.pluck($scope.payments.Items, 'Amount'), function(a, b) {return a + b; });
+ payment.MaxAmount = (payment.Amount + maxAmount).toFixed(2);
+ });
+ $scope.canAddPayment = paymentTotal < $scope.order.Total;
+ if($scope.OCPayments) $scope.OCPayments.$setValidity('Insufficient_Payment', !$scope.canAddPayment);
+ }
+}
\ No newline at end of file
diff --git a/src/app/checkout/payment/directives/templates/creditCard.tpl.html b/src/app/checkout/payment/directives/templates/creditCard.tpl.html
new file mode 100644
index 00000000..2153c235
--- /dev/null
+++ b/src/app/checkout/payment/directives/templates/creditCard.tpl.html
@@ -0,0 +1,23 @@
+
+
+
Credit Card
+
Create Credit Card
+
+
+
+
+
+
+
+ {{creditCard.CardholderName}}
+ {{'XXXX-XXXX-XXXX-' + creditCard.PartialAccountNumber}}
+ Expires On: {{creditCard.ExpirationDate | date:'MM/yy'}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/checkout/payment/directives/templates/payment.tpl.html b/src/app/checkout/payment/directives/templates/payment.tpl.html
new file mode 100644
index 00000000..83e9f992
--- /dev/null
+++ b/src/app/checkout/payment/directives/templates/payment.tpl.html
@@ -0,0 +1,16 @@
+
+
+
+ Payment Method
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/checkout/payment/directives/templates/payments.tpl.html b/src/app/checkout/payment/directives/templates/payments.tpl.html
new file mode 100644
index 00000000..0e3ee8b6
--- /dev/null
+++ b/src/app/checkout/payment/directives/templates/payments.tpl.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Amount
+
+
+
+
+
+
+
+ Remove Payment
+
+
+
+
+
+
Add New Payment
+
\ No newline at end of file
diff --git a/src/app/checkout/payment/directives/templates/purchaseOrder.tpl.html b/src/app/checkout/payment/directives/templates/purchaseOrder.tpl.html
new file mode 100644
index 00000000..ede0b44d
--- /dev/null
+++ b/src/app/checkout/payment/directives/templates/purchaseOrder.tpl.html
@@ -0,0 +1,7 @@
+
+
+
+ PO Number
+
+
+
\ No newline at end of file
diff --git a/src/app/checkout/payment/directives/templates/spendingAccount.tpl.html b/src/app/checkout/payment/directives/templates/spendingAccount.tpl.html
new file mode 100644
index 00000000..a09ba30b
--- /dev/null
+++ b/src/app/checkout/payment/directives/templates/spendingAccount.tpl.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
{{spendingAccount.Name}}
+
+
+ Lifetime: {{spendingAccount.StartDate | date:'shortDate'}} - {{spendingAccount.EndDate | date :'shortDate'}}
+
+
+ Made Available On: {{spendingAccount.StartDate | date:'shortDate'}}
+
+
+ Expires On: {{spendingAccount.EndDate | date :'shortDate'}}
+
+
+
+
+
+
{{spendingAccount.Balance | currency}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/checkout/payment/templates/checkout.payment.tpl.html b/src/app/checkout/payment/templates/checkout.payment.tpl.html
new file mode 100644
index 00000000..5e5105ae
--- /dev/null
+++ b/src/app/checkout/payment/templates/checkout.payment.tpl.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+ Order Summary
+
+
+
+
+
+
+
Subtotal: {{base.currentOrder.Subtotal | currency}}
+
Estimated Shipping: + {{base.currentOrder.ShippingCost | currency}}
+
+ {{promotion.Code}}
+ - {{promotion.Amount | currency}}
+
+
Estimated Total: {{base.currentOrder.Total | currency}}
+
+
+
+
+
+
diff --git a/src/app/checkout/review/checkout.review.js b/src/app/checkout/review/checkout.review.js
new file mode 100644
index 00000000..82a97f9e
--- /dev/null
+++ b/src/app/checkout/review/checkout.review.js
@@ -0,0 +1,83 @@
+angular.module('orderCloud')
+ .config(checkoutReviewConfig)
+ .controller('CheckoutReviewCtrl', CheckoutReviewController);
+
+function checkoutReviewConfig($stateProvider) {
+ $stateProvider
+ .state('checkout.review', {
+ url: '/review',
+ templateUrl: 'checkout/review/templates/checkout.review.tpl.html',
+ controller: 'CheckoutReviewCtrl',
+ controllerAs: 'checkoutReview',
+ resolve: {
+ LineItemsList: function($q, $state, toastr, OrderCloud, ocLineItems, CurrentOrder) {
+ var dfd = $q.defer();
+ OrderCloud.LineItems.List(CurrentOrder.ID)
+ .then(function(data) {
+ if (!data.Items.length) {
+ dfd.resolve(data);
+ }
+ else {
+ ocLineItems.GetProductInfo(data.Items)
+ .then(function() {
+ dfd.resolve(data);
+ });
+ }
+ })
+ .catch(function() {
+ toastr.error('Your order does not contain any line items.', 'Error');
+ dfd.reject();
+ });
+ return dfd.promise;
+ },
+ OrderPaymentsDetail: function($q, OrderCloud, CurrentOrder, $state) {
+ return OrderCloud.Payments.List(CurrentOrder.ID)
+ .then(function(data) {
+ //TODO: create a queue that can be resolved
+ var dfd = $q.defer();
+ if (!data.Items.length) {
+ dfd.reject();
+ $state.go('checkout.shipping');
+ }
+ var queue = [];
+ angular.forEach(data.Items, function(payment, index) {
+ if (payment.Type === 'CreditCard' && payment.CreditCardID) {
+ queue.push((function() {
+ var d = $q.defer();
+ OrderCloud.Me.GetCreditCard(payment.CreditCardID)
+ .then(function(cc) {
+ data.Items[index].Details = cc;
+ d.resolve();
+ });
+ return d.promise;
+ })());
+ }
+ if (payment.Type === 'SpendingAccount' && payment.SpendingAccountID) {
+ queue.push((function() {
+ var d = $q.defer();
+ OrderCloud.Me.GetSpendingAccount(payment.SpendingAccountID)
+ .then(function(sa) {
+ data.Items[index].Details = sa;
+ d.resolve();
+ });
+ return d.resolve();
+ })());
+ }
+ });
+ $q.all(queue)
+ .then(function() {
+ dfd.resolve(data);
+ });
+ return dfd.promise;
+ })
+
+ }
+ }
+ });
+}
+
+function CheckoutReviewController(LineItemsList, OrderPaymentsDetail) {
+ var vm = this;
+ vm.payments = OrderPaymentsDetail;
+ vm.lineItems = LineItemsList;
+}
\ No newline at end of file
diff --git a/src/app/checkout/review/templates/checkout.review.tpl.html b/src/app/checkout/review/templates/checkout.review.tpl.html
new file mode 100644
index 00000000..4324ec2e
--- /dev/null
+++ b/src/app/checkout/review/templates/checkout.review.tpl.html
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{lineItem.ProductID}}
+
+
+ {{spec.Name}}:
+ {{spec.Value}}
+
+
+
+
+
+
+
{{lineItem.UnitPrice | currency}}
+
+
+
+ {{lineItem.Quantity}}
+
+
+ {{'x ' + lineItem.Product.QuantityMultiplier + (lineItem.Quantity ? (' (' + (lineItem.Quantity * lineItem.Product.QuantityMultiplier) + ')') : '')}}
+
+
+
+
{{lineItem.LineTotal | currency}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Order Summary
+
+
+
+
+
+
+
Subtotal: {{base.currentOrder.Subtotal | currency}}
+
Estimated Shipping: + {{base.currentOrder.ShippingCost | currency}}
+
+ {{promotion.Code}}
+ - {{promotion.Amount | currency}}
+
+
Total: {{base.currentOrder.Total | currency}}
+
+
+
{{payment.Type | humanize}} {{payment.Amount | currency}}
+
+
PO#: {{payment.xp.PONumber}}
+
+
+
+ XXXX-XXXX-XXXX- {{payment.Details.PartialAccountNumber}}
+
+
+
+ {{payment.Details.Name}}
+ Remaining Balance: {{payment.Details.Balance | currency}}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/checkout/shipping/checkout.shipping.js b/src/app/checkout/shipping/checkout.shipping.js
new file mode 100644
index 00000000..9f30c47a
--- /dev/null
+++ b/src/app/checkout/shipping/checkout.shipping.js
@@ -0,0 +1,83 @@
+angular.module('orderCloud')
+ .config(checkoutShippingConfig)
+ .controller('CheckoutShippingCtrl', CheckoutShippingController);
+
+function checkoutShippingConfig($stateProvider) {
+ $stateProvider
+ .state('checkout.shipping', {
+ url: '/shipping',
+ templateUrl: 'checkout/shipping/templates/checkout.shipping.tpl.html',
+ controller: 'CheckoutShippingCtrl',
+ controllerAs: 'checkoutShipping'
+ });
+}
+
+function CheckoutShippingController($exceptionHandler, $rootScope, toastr, OrderCloud, MyAddressesModal, AddressSelectModal, ShippingRates, CheckoutConfig) {
+ var vm = this;
+ vm.createAddress = createAddress;
+ vm.changeShippingAddress = changeShippingAddress;
+ vm.saveShipAddress = saveShipAddress;
+ vm.shipperSelected = shipperSelected;
+ vm.initShippingRates = initShippingRates;
+ vm.getShippingRates = getShippingRates;
+ vm.analyzeShipments = analyzeShipments;
+
+ function createAddress(order) {
+ MyAddressesModal.Create()
+ .then(function(address) {
+ toastr.success('Address Created', 'Success');
+ order.ShippingAddressID = address.ID;
+ vm.saveShipAddress(order);
+ });
+ }
+
+ function changeShippingAddress(order) {
+ AddressSelectModal.Open('shipping')
+ .then(function(address) {
+ if (address == 'create') {
+ vm.createAddress(order);
+ } else {
+ order.ShippingAddressID = address.ID;
+ vm.saveShipAddress(order);
+ }
+ })
+ }
+
+ function saveShipAddress(order) {
+ if (order && order.ShippingAddressID) {
+ OrderCloud.Orders.Patch(order.ID, {ShippingAddressID: order.ShippingAddressID})
+ .then(function(updatedOrder) {
+ $rootScope.$broadcast('OC:OrderShipAddressUpdated', updatedOrder);
+ vm.getShippingRates(order);
+ })
+ .catch(function(ex){
+ $exceptionHandler(ex);
+ });
+ }
+ }
+
+ function initShippingRates(order) {
+ if (CheckoutConfig.ShippingRates && order.ShippingAddressID) vm.getShippingRates(order);
+ }
+
+ function getShippingRates(order) {
+ vm.shippersAreLoading = true;
+ vm.shippersLoading = ShippingRates.GetRates(order)
+ .then(function(shipments) {
+ vm.shippersAreLoading = false;
+ vm.shippingRates = shipments;
+ vm.analyzeShipments(order);
+ });
+ }
+
+ function analyzeShipments(order) {
+ vm.shippingRates = ShippingRates.AnalyzeShipments(order, vm.shippingRates);
+ }
+
+ function shipperSelected(order) {
+ ShippingRates.ManageShipments(order, vm.shippingRates)
+ .then(function() {
+ $rootScope.$broadcast('OC:UpdateOrder', order.ID);
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/app/checkout/shipping/checkout.shipping.rates.js b/src/app/checkout/shipping/checkout.shipping.rates.js
new file mode 100644
index 00000000..0dc8bb62
--- /dev/null
+++ b/src/app/checkout/shipping/checkout.shipping.rates.js
@@ -0,0 +1,131 @@
+angular.module('orderCloud')
+ .factory('ShippingRates', ShippingRatesService)
+;
+
+function ShippingRatesService($q, $resource, OrderCloud, apiurl) {
+ var service = {
+ GetRates: _getRates,
+ GetLineItemRates: _getLineItemRates,
+ SetShippingCost: _setShippingCost,
+ ManageShipments: _manageShipments,
+ AnalyzeShipments: _analyzeShipments
+ };
+
+ var shippingRatesURL = apiurl + '/v1/integrationproxy/shippingrates';
+
+ function _getRates(order) {
+ var deferred = $q.defer();
+
+ var request = {
+ BuyerID: OrderCloud.BuyerID.Get(),
+ TransactionType: 'GetRates',
+ OrderID: order.ID
+ };
+
+ $resource(shippingRatesURL, {}, {getrates: {method: 'POST', headers: {'Authorization': 'Bearer ' + OrderCloud.Auth.ReadToken()}}}).getrates(request).$promise
+ .then(function(data) {
+ deferred.resolve(data.ResponseBody.Shipments);
+ })
+ .catch(function(ex) {
+ deferred.resolve(null);
+ });
+
+ return deferred.promise;
+ }
+
+ function _getLineItemRates(order) {
+ var deferred = $q.defer();
+
+ var request = {
+ BuyerID: OrderCloud.BuyerID.Get(),
+ TransactionType: 'GetLineItemRates',
+ OrderID: order.ID
+ };
+
+ $resource(shippingRatesURL, {}, {getlineitemrates: {method: 'POST', headers: {'Authorization': 'Bearer ' + OrderCloud.Auth.ReadToken()}}}).getlineitemrates(request).$promise
+ .then(function(data) {
+ deferred.resolve(data.ResponseBody.Shipments);
+ })
+ .catch(function(ex) {
+ deferred.resolve(null);
+ });
+
+ return deferred.promise;
+ }
+
+ function _setShippingCost(order, cost) {
+ var deferred = $q.defer();
+
+ var request = {
+ BuyerID: OrderCloud.BuyerID.Get(),
+ TransactionType: 'SetShippingCost',
+ OrderID: order.ID,
+ ShippingCost: cost
+ };
+
+ $resource(shippingRatesURL, {}, {setshippingcost: {method: 'POST', headers: {'Authorization': 'Bearer ' + OrderCloud.Auth.ReadToken()}}}).setshippingcost(request).$promise
+ .then(function(data) {
+ deferred.resolve(data.ResponseBody);
+ })
+ .catch(function(ex) {
+ deferred.resolve(null);
+ });
+
+ return deferred.promise;
+ }
+
+ function _manageShipments(order, shipments) {
+ var deferred = $q.defer();
+
+ var xpPatch = {Shippers: []};
+ var shippingCost = 0;
+
+ angular.forEach(shipments, function(shipment) {
+ if (shipment.SelectedShipper) {
+ shippingCost += shipment.SelectedShipper.Price;
+ xpPatch.Shippers.push({
+ Shipper: shipment.SelectedShipper.Description,
+ Cost: shipment.SelectedShipper.Price,
+ LineItemIDs: shipment.LineItemIDs
+ });
+ }
+ });
+
+ OrderCloud.Orders.Patch(order.ID, {xp: xpPatch})
+ .then(function() {
+ updateShippingCost();
+ })
+ .catch(function() {
+ deferred.reject();
+ });
+
+ function updateShippingCost() {
+ _setShippingCost(order, shippingCost)
+ .then(function(data) {
+ deferred.resolve(data);
+ })
+ .catch(function(ex) {
+ deferred.reject();
+ });
+ }
+
+ return deferred.promise;
+ }
+
+ function _analyzeShipments(order, shippingRates) {
+ if (order.xp && order.xp.Shippers) {
+ angular.forEach(order.xp.Shippers, function(shipment) {
+ angular.forEach(shippingRates, function(s) {
+ if (_.intersection(s.LineItemIDs, shipment.LineItemIDs).length == shipment.LineItemIDs.length) {
+ var selection = _.findWhere(s.Rates, {Description: shipment.Shipper});
+ if (selection) s.SelectedShipper = selection;
+ }
+ });
+ });
+ }
+
+ return shippingRates;
+ }
+
+ return service;
+}
diff --git a/src/app/checkout/shipping/templates/checkout.shipping.tpl.html b/src/app/checkout/shipping/templates/checkout.shipping.tpl.html
new file mode 100644
index 00000000..d79915a5
--- /dev/null
+++ b/src/app/checkout/shipping/templates/checkout.shipping.tpl.html
@@ -0,0 +1,66 @@
+
+
+
+
+
New Address
+
Delivery Address
+
+
+ You currently do not have a shipping address selected.
+
Select one now
+
+
+
+ Shipping Method
+
+
+
+
+
+
+
+
+ Order Summary
+
+
+
+
+
+
+
Subtotal: {{base.currentOrder.Subtotal | currency}}
+
Estimated Shipping: + {{base.currentOrder.ShippingCost | currency}}
+
+
+ {{promotion.Code}}
+ remove
+ - {{promotion.Amount | currency}}
+
+
Estimated Total: {{base.currentOrder.Total | currency}}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/checkout/templates/addressSelect.modal.tpl.html b/src/app/checkout/templates/addressSelect.modal.tpl.html
new file mode 100644
index 00000000..095f5fa2
--- /dev/null
+++ b/src/app/checkout/templates/addressSelect.modal.tpl.html
@@ -0,0 +1,22 @@
+
+
+
+ You currently do not have any addresses available.
+
Create one now
+
+
+
\ No newline at end of file
diff --git a/src/app/checkout/templates/checkout.tpl.html b/src/app/checkout/templates/checkout.tpl.html
new file mode 100644
index 00000000..1fab3497
--- /dev/null
+++ b/src/app/checkout/templates/checkout.tpl.html
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/src/app/checkout/tests/checkout.confirmation.spec.js b/src/app/checkout/tests/checkout.confirmation.spec.js
new file mode 100644
index 00000000..2555ea12
--- /dev/null
+++ b/src/app/checkout/tests/checkout.confirmation.spec.js
@@ -0,0 +1,164 @@
+describe('Component: Checkout Confirmation', function() {
+ var scope,
+ q,
+ oc,
+ lineItemHelpers,
+ lineItemsList,
+ orderPayments,
+ order,
+ submittedOrder,
+ address;
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(module(function($provide) {
+ $provide.value('OrderPayments', {
+ Items: [{Type: 'CreditCard', CreditCardID: 'CC123'}, {Type: 'SpendingAccount', SpendingAccountID: 'SA123'}],
+ Meta: {
+ Page: 1,
+ PageSize: 20,
+ TotalCount: 0,
+ TotalPages: 1,
+ ItemRange: [1, 0]
+ }
+ });
+ $provide.value('SubmittedOrder', {
+ ID: 'SubmittedOrder123',
+ BillingAddressID: 'TestAddress123456789',
+ ShippingAddressID: 'TestAddress123456789',
+ BillingAddress: {
+ ID: 'TestAddress123456789'
+ }
+ });
+ }));
+ beforeEach(inject(function($q, $rootScope, OrderCloud, ocLineItems, SubmittedOrder, OrderPayments) {
+ q = $q;
+ oc = OrderCloud;
+ scope = $rootScope.$new();
+ lineItemHelpers = ocLineItems;
+ submittedOrder = SubmittedOrder;
+ orderPayments = OrderPayments;
+ order = {
+ ID: 'TestOrder123456789',
+ Type: 'Standard',
+ FromUserID: 'TestUser123456789',
+ BillingAddressID: 'TestAddress123456789',
+ BillingAddress: {
+ ID: 'TestAddress123456789'
+ },
+ ShippingAddressID: 'TestAddress123456789',
+ Comments: null,
+ ShippingCost: null,
+ TaxCost: null,
+ Subtotal: 10,
+ Total: 10
+ };
+ lineItemsList = {
+ Items : [{ID: '1'}, {ID: '2'}],
+ Meta : {
+ Page: 1,
+ PageSize: 20,
+ TotalCount: 2,
+ TotalPages: 1,
+ ItemRange: [1,2]
+ }
+ };
+ address = {
+ ID: 'TestAddress123456789'
+ };
+ }));
+
+ describe('State: confirmation', function() {
+ var state, stateParams;
+ beforeEach(inject(function($state, $stateParams) {
+ state = $state.get('confirmation');
+ stateParams = $stateParams;
+ stateParams.orderid = "SubmittedOrder123";
+ var submittedOrderDefer = q.defer();
+ submittedOrderDefer.resolve(submittedOrder);
+ spyOn(oc.Me, 'GetOrder').and.returnValue(submittedOrderDefer.promise);
+
+ var defer = q.defer();
+ defer.resolve();
+ spyOn(oc.Me, 'GetAddress').and.returnValue(defer.promise);
+ spyOn(oc.Orders, 'ListPromotions').and.returnValue(defer.promise);
+
+ var paymentsDefer = q.defer();
+ paymentsDefer.resolve(orderPayments);
+ spyOn(oc.Payments, 'List').and.returnValue(paymentsDefer.promise);
+ spyOn(oc.Me, 'GetCreditCard').and.returnValue(defer.promise);
+ spyOn(oc.Me, 'GetSpendingAccount').and.returnValue(defer.promise);
+
+ var lineItemListDefer = q.defer();
+ lineItemListDefer.resolve(lineItemsList);
+ spyOn(oc.LineItems, 'List').and.returnValue(lineItemListDefer.promise);
+ spyOn(lineItemHelpers, 'GetProductInfo').and.returnValue(lineItemListDefer.promise);
+ }));
+ it('should call Me.GetOrder for submitted order', inject(function($injector) {
+ $injector.invoke(state.resolve.SubmittedOrder);
+ expect(oc.Me.GetOrder).toHaveBeenCalledWith('SubmittedOrder123');
+ }));
+ it('should call Me.GetAddress for ShippingAddressID', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderShipAddress);
+ expect(oc.Me.GetAddress).toHaveBeenCalledWith(submittedOrder.ShippingAddressID);
+ }));
+ it('should call Orders.ListPromotions', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderPromotions);
+ expect(oc.Orders.ListPromotions).toHaveBeenCalledWith(submittedOrder.ID);
+ }));
+ it('should call Me.GetAddress for BillingAddressID', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderBillingAddress);
+ expect(oc.Me.GetAddress).toHaveBeenCalledWith(submittedOrder.BillingAddressID);
+ }));
+ it('should call Payments.List', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderPayments);
+ expect(oc.Payments.List).toHaveBeenCalledWith(submittedOrder.ID);
+ }));
+ it('should call Me.GetCreditCard for first payment', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderPayments);
+ scope.$digest();
+ expect(oc.Me.GetCreditCard).toHaveBeenCalledWith(orderPayments.Items[0].CreditCardID);
+ }));
+ it('should call Me.GetSpendingAccount for second payment', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderPayments);
+ scope.$digest();
+ expect(oc.Me.GetSpendingAccount).toHaveBeenCalledWith(orderPayments.Items[1].SpendingAccountID);
+ }));
+ it('should call LineItems.List',inject(function($injector){
+ $injector.invoke(state.resolve.LineItemsList);
+ expect(oc.LineItems.List).toHaveBeenCalledWith(submittedOrder.ID);
+ }));
+ it('should call LineItemHelper', inject(function($injector){
+ $injector.invoke(state.resolve.LineItemsList);
+ scope.$digest();
+ expect(lineItemHelpers.GetProductInfo).toHaveBeenCalled();
+ }));
+ });
+
+ describe('Controller: ConfirmationCtrl', function(){
+ var confirmCtrl,
+ SubmittedOrder = 'FAKE_ORDER',
+ OrderShipAddress = 'FAKE_SHIP_ADDRESS',
+ OrderPromotions = {Items: 'FAKE_PROMOTIONS'},
+ OrderBillingAddress = 'FAKE_BILL_ADDRESS',
+ OrderPayments = {Items: 'FAKE_PAYMENTS'},
+ LineItemsList = 'FAKE_LINE_ITEMS';
+ beforeEach(inject(function($controller) {
+ confirmCtrl = $controller('CheckoutConfirmationCtrl', {
+ SubmittedOrder: SubmittedOrder,
+ OrderShipAddress: OrderShipAddress,
+ OrderPromotions: OrderPromotions,
+ OrderBillingAddress: OrderBillingAddress,
+ OrderPayments: OrderPayments,
+ LineItemsList: LineItemsList
+ });
+ }));
+ it ('should initialize the resolves into the controller view model', function() {
+ expect(confirmCtrl.order).toBe(SubmittedOrder);
+ expect(confirmCtrl.shippingAddress).toBe(OrderShipAddress);
+ expect(confirmCtrl.promotions).toBe('FAKE_PROMOTIONS');
+ expect(confirmCtrl.billingAddress).toBe(OrderBillingAddress);
+ expect(confirmCtrl.payments).toBe('FAKE_PAYMENTS');
+ expect(confirmCtrl.lineItems).toBe(LineItemsList);
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/app/checkout/tests/checkout.payment.spec.js b/src/app/checkout/tests/checkout.payment.spec.js
new file mode 100644
index 00000000..d53093bb
--- /dev/null
+++ b/src/app/checkout/tests/checkout.payment.spec.js
@@ -0,0 +1,119 @@
+describe('Component: Checkout Payment', function() {
+ var scope,
+ q,
+ oc,
+ order,
+ address,
+ paymentListItems;
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(inject(function ($q, $rootScope, OrderCloud) {
+ q = $q;
+ oc = OrderCloud;
+ scope = $rootScope.$new();
+ order = {
+ ID: 'TestOrder123456789',
+ Type: 'Standard',
+ FromUserID: 'TestUser123456789',
+ BillingAddressID: 'TestAddress123456789',
+ BillingAddress: {
+ ID: 'TestAddress123456789'
+ },
+ ShippingAddressID: 'TestAddress123456789',
+ Comments: null,
+ ShippingCost: null,
+ TaxCost: null,
+ Subtotal: 10,
+ Total: 10
+ };
+ address = {
+ ID: 'TestAddress123456789'
+ };
+ paymentListItems = {
+ Items: [{Type: 'CreditCard', CreditCardID: 'CC123', Amount: 5}, {Type: 'SpendingAccount', SpendingAccountID: 'SA123', Amount: 5}],
+ Meta: {
+ Page: 1,
+ PageSize: 20,
+ TotalCount: 0,
+ TotalPages: 1,
+ ItemRange: [1, 0]
+ }
+ };
+ }));
+
+ describe('Controller: CheckoutPaymentCtrl', function() {
+ var checkoutPaymentCtrl,
+ addressSelectModal,
+ myAddressModal,
+ rootScope;
+ beforeEach(inject(function($rootScope, $controller, AddressSelectModal, MyAddressesModal) {
+ addressSelectModal = AddressSelectModal;
+ myAddressModal = MyAddressesModal;
+ checkoutPaymentCtrl = $controller('CheckoutPaymentCtrl');
+ rootScope = $rootScope;
+ }));
+
+ describe('Function: createAddress', function() {
+ beforeEach(function() {
+ var df = q.defer();
+ df.resolve({ID:'ADDRESS_ID'});
+ spyOn(myAddressModal, 'Create').and.returnValue(df.promise);
+
+ var defer = q.defer();
+ defer.resolve('UPDATED_ORDER');
+ spyOn(oc.Orders, 'Patch').and.returnValue(defer.promise);
+
+ spyOn(rootScope, '$broadcast').and.callThrough();
+
+ checkoutPaymentCtrl.createAddress(order);
+ });
+ it ('should call MyAddressModal.Create()', function() {
+ expect(myAddressModal.Create).toHaveBeenCalled();
+ });
+ it ('should set the order.BillingAddressID to the new address ID', function() {
+ scope.$digest();
+ expect(order.BillingAddressID).toBe('ADDRESS_ID');
+ });
+ it ('should patch the order with the new order.BillingAddressID', function() {
+ scope.$digest();
+ expect(oc.Orders.Patch).toHaveBeenCalledWith(order.ID, {BillingAddressID: 'ADDRESS_ID'});
+ });
+ it ('should broadcast a $rootScope event "OC:OrderBillAddressUpdated"', function() {
+ scope.$digest();
+ expect(rootScope.$broadcast).toHaveBeenCalledWith('OC:OrderBillAddressUpdated', 'UPDATED_ORDER');
+ })
+ });
+
+ describe('Function: changeBillingAddress', function() {
+ beforeEach(function() {
+ var df = q.defer();
+ df.resolve({ID:'ADDRESS_ID'});
+ spyOn(addressSelectModal, 'Open').and.returnValue(df.promise);
+
+ var defer = q.defer();
+ defer.resolve('UPDATED_ORDER');
+ spyOn(oc.Orders, 'Patch').and.returnValue(defer.promise);
+
+ spyOn(rootScope, '$broadcast').and.callThrough();
+
+ checkoutPaymentCtrl.changeBillingAddress(order);
+ });
+ it ('should call AddressSelectModal.Open() with "billing"', function() {
+ expect(addressSelectModal.Open).toHaveBeenCalledWith('billing');
+
+ });
+ it ('should set the order.BillingAddressID to the new address ID', function() {
+ scope.$digest();
+ expect(order.BillingAddressID).toBe('ADDRESS_ID');
+ });
+ it ('should patch the order with the new order.BillingAddressID', function() {
+ scope.$digest();
+ expect(oc.Orders.Patch).toHaveBeenCalledWith(order.ID, {BillingAddressID: 'ADDRESS_ID'});
+ });
+ it ('should broadcast a $rootScope event "OC:OrderBillAddressUpdated"', function() {
+ scope.$digest();
+ expect(rootScope.$broadcast).toHaveBeenCalledWith('OC:OrderBillAddressUpdated', 'UPDATED_ORDER');
+ })
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/app/checkout/tests/checkout.review.spec.js b/src/app/checkout/tests/checkout.review.spec.js
new file mode 100644
index 00000000..a47ffae4
--- /dev/null
+++ b/src/app/checkout/tests/checkout.review.spec.js
@@ -0,0 +1,124 @@
+describe('Component: Checkout Review', function() {
+ var scope,
+ q,
+ oc,
+ lineItemHelpers,
+ lineItemsList,
+ order,
+ currentOrder,
+ orderPayments;
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(module(function($provide) {
+ $provide.value('CurrentOrder', {
+ ID: 'CurrentOrder123',
+ BillingAddressID: 'TestAddress123456789',
+ ShippingAddressID: 'TestAddress123456789',
+ BillingAddress: {
+ ID: 'TestAddress123456789'
+ }
+ });
+ $provide.value('OrderPayments', {
+ Items: [{Type: 'CreditCard', CreditCardID: 'CC123'}, {Type: 'SpendingAccount', SpendingAccountID: 'SA123'}],
+ Meta: {
+ Page: 1,
+ PageSize: 20,
+ TotalCount: 0,
+ TotalPages: 1,
+ ItemRange: [1, 0]
+ }
+ });
+ }));
+ beforeEach(inject(function($q, $rootScope, OrderCloud, CurrentOrder, OrderPayments, ocLineItems) {
+ q = $q;
+ oc = OrderCloud;
+ scope = $rootScope.$new();
+ lineItemHelpers = ocLineItems;
+ currentOrder = CurrentOrder;
+ orderPayments = OrderPayments;
+ order = {
+ ID: 'TestOrder123456789',
+ Type: 'Standard',
+ FromUserID: 'TestUser123456789',
+ BillingAddressID: 'TestAddress123456789',
+ BillingAddress: {
+ ID: 'TestAddress123456789'
+ },
+ ShippingAddressID: 'TestAddress123456789',
+ Comments: null,
+ ShippingCost: null,
+ TaxCost: null,
+ Subtotal: 10,
+ Total: 10
+ };
+ lineItemsList = {
+ Items : [{ID: '1'}, {ID: '2'}],
+ Meta : {
+ Page: 1,
+ PageSize: 20,
+ TotalCount: 2,
+ TotalPages: 1,
+ ItemRange: [1,2]
+ }
+ };
+ }));
+
+ describe('State: checkout.review', function() {
+ var state;
+ beforeEach(inject(function($state) {
+ state = $state.get('checkout.review');
+ var lineItemListDefer = q.defer();
+ lineItemListDefer.resolve(lineItemsList);
+ spyOn(oc.LineItems, 'List').and.returnValue(lineItemListDefer.promise);
+ spyOn(lineItemHelpers, 'GetProductInfo').and.returnValue(lineItemListDefer.promise);
+
+ var defer = q.defer();
+ defer.resolve();
+ var paymentsDefer = q.defer();
+ paymentsDefer.resolve(orderPayments);
+ spyOn(oc.Payments, 'List').and.returnValue(paymentsDefer.promise);
+ spyOn(oc.Me, 'GetCreditCard').and.returnValue(defer.promise);
+ spyOn(oc.Me, 'GetSpendingAccount').and.returnValue(defer.promise);
+ }));
+ it('should call LineItems.List', inject(function($injector){
+ $injector.invoke(state.resolve.LineItemsList);
+ expect(oc.LineItems.List).toHaveBeenCalledWith(currentOrder.ID);
+ }));
+ it('should call LineItemHelper', inject(function($injector){
+ $injector.invoke(state.resolve.LineItemsList);
+ scope.$digest();
+ expect(lineItemHelpers.GetProductInfo).toHaveBeenCalled();
+ }));
+ it('should call Payment List method', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderPaymentsDetail);
+ scope.$digest();
+ expect(oc.Payments.List).toHaveBeenCalledWith(currentOrder.ID);
+ }));
+ it('should call Me.GetCreditCard for first payment', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderPaymentsDetail);
+ scope.$digest();
+ expect(oc.Me.GetCreditCard).toHaveBeenCalledWith(orderPayments.Items[0].CreditCardID);
+ }));
+ it('should call Me.GetSpendingAccount for second payment', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderPaymentsDetail);
+ scope.$digest();
+ expect(oc.Me.GetSpendingAccount).toHaveBeenCalledWith(orderPayments.Items[1].SpendingAccountID);
+ }));
+ });
+
+ describe('Controller: CheckoutReviewCtrl', function(){
+ var reviewCtrl,
+ OrderPaymentsDetail = 'FAKE_PAYMENTS',
+ LineItemsList = 'FAKE_LINE_ITEMS';
+ beforeEach(inject(function($controller) {
+ reviewCtrl = $controller('CheckoutReviewCtrl', {
+ OrderPaymentsDetail: OrderPaymentsDetail,
+ LineItemsList: LineItemsList
+ });
+ }));
+ it ('should initialize the resolves into the controller view model', function() {
+ expect(reviewCtrl.payments).toBe(OrderPaymentsDetail);
+ expect(reviewCtrl.lineItems).toBe(LineItemsList);
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/app/checkout/tests/checkout.shipping.spec.js b/src/app/checkout/tests/checkout.shipping.spec.js
new file mode 100644
index 00000000..ee4b21df
--- /dev/null
+++ b/src/app/checkout/tests/checkout.shipping.spec.js
@@ -0,0 +1,247 @@
+describe('Component: Checkout Shipping', function() {
+ var scope,
+ rootScope,
+ q,
+ oc,
+ order,
+ checkoutConfig,
+ address,
+ rates;
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(module(function($provide) {
+ $provide.value('CheckoutConfig', {
+ ShippingRates: true,
+ TaxRates: false
+ });
+ }));
+ beforeEach(inject(function($q, $rootScope, OrderCloud, CheckoutConfig) {
+ q = $q;
+ oc = OrderCloud;
+ scope = $rootScope.$new();
+ rootScope = $rootScope;
+ checkoutConfig = CheckoutConfig;
+ order = {
+ ID: 'TestOrder123456789',
+ Type: 'Standard',
+ FromUserID: 'TestUser123456789',
+ BillingAddressID: 'TestAddress123456789',
+ BillingAddress: {
+ ID: 'TestAddress123456789'
+ },
+ ShippingAddressID: 'TestAddress123456789',
+ Comments: null,
+ ShippingCost: null,
+ TaxCost: null,
+ Subtotal: 10,
+ Total: 10
+ };
+ address = {
+ ID: 'TestAddress123456789'
+ };
+ rates = {
+ Shipments: [
+ {
+ Weight: 10,
+ ShipFromAddressID: '1234',
+ ShipToAddressID: '2345',
+ LineItemIDs: [
+ '1',
+ '2'
+ ],
+ Rates: [
+ {
+ Price: 6,
+ Description: 'UPS Standard'
+ },
+ {
+ Price: 20,
+ Description: 'UPS Next Day Air'
+ }
+ ],
+ SelectedShipper: {
+ Price: 6,
+ Description: 'UPS Standard'
+ }
+ }
+ ]
+ };
+ }));
+
+ describe('Controller: CheckoutShippingCtrl', function() {
+ var checkoutShippingController,
+ toaster,
+ myAddressesModal,
+ addressSelectModal,
+ shippingRates,
+ mockModal;
+ beforeEach(inject(function($state, $controller, toastr, MyAddressesModal, AddressSelectModal, ShippingRates) {
+ toaster = toastr;
+ state = $state;
+ myAddressesModal = MyAddressesModal;
+ addressSelectModal = AddressSelectModal;
+ shippingRates = ShippingRates;
+
+ mockModal = {
+ result: {
+ then: function(confirmCallBack, cancelCallBack) {
+ this.confirmCallBack = confirmCallBack;
+ this.cancelCallBack = cancelCallBack;
+ }
+ },
+ close: function(item) {
+ this.result.confirmCallBack(item);
+ },
+ dismiss: function(type) {
+ this.result.cancelCallBack(type);
+ }
+ };
+
+ checkoutShippingController = $controller('CheckoutShippingCtrl', {
+ $scope: scope
+ });
+
+ var defer = q.defer();
+ defer.resolve();
+
+ var orderDefer = q.defer();
+ orderDefer.resolve(order);
+
+ var shippingRatesDefer = q.defer();
+ shippingRatesDefer.resolve(rates.Shipments);
+
+ spyOn(myAddressesModal, 'Create').and.returnValue(mockModal.result);
+ spyOn(toaster, 'success');
+ spyOn(addressSelectModal, 'Open').and.returnValue(mockModal.result);
+ spyOn(oc.Orders, 'Patch').and.returnValue(orderDefer.promise);
+ spyOn(rootScope, '$broadcast').and.returnValue(true);
+ spyOn(shippingRates, 'GetRates').and.returnValue(shippingRatesDefer.promise);
+ spyOn(shippingRates, 'AnalyzeShipments').and.returnValue(shippingRatesDefer.promise);
+ spyOn(shippingRates, 'ManageShipments').and.returnValue(defer.promise);
+ }));
+
+ describe('createAddress', function() {
+ beforeEach(inject(function() {
+ checkoutShippingController.createAddress(order);
+ scope.$digest();
+ }));
+ it('should call MyAddressModal Create method', function() {
+ expect(myAddressesModal.Create).toHaveBeenCalled();
+ });
+ it('should call toastr success after address is created', function() {
+ mockModal.close(address);
+ expect(toaster.success).toHaveBeenCalled();
+ });
+ it('should call saveShipAddress which patches order', function() {
+ checkoutShippingController.saveShipAddress(order);
+ expect(oc.Orders.Patch).toHaveBeenCalledWith(order.ID, {ShippingAddressID: order.ShippingAddressID});
+ })
+ });
+
+ describe('changeShippingAddress', function() {
+ beforeEach(function() {
+ checkoutShippingController.changeShippingAddress(order);
+ scope.$digest();
+ });
+ it('should call AddressSelectModal Open method with "shipping"', function() {
+ expect(addressSelectModal.Open).toHaveBeenCalledWith('shipping');
+ });
+ it('should call createAddress if "create" is returned', function() {
+ mockModal.close('create');
+ checkoutShippingController.createAddress(order);
+ expect(myAddressesModal.Create).toHaveBeenCalled();
+ });
+ it('should call saveShipAddress if nothing address is returned', function() {
+ mockModal.close(address);
+ checkoutShippingController.saveShipAddress(order);
+ expect(oc.Orders.Patch).toHaveBeenCalledWith(order.ID, {ShippingAddressID: address.ID});
+ });
+ });
+
+ describe('saveShipAddress', function() {
+ beforeEach(function() {
+ checkoutShippingController.saveShipAddress(order);
+ scope.$digest();
+ });
+ it('should call Orders Patch method', function() {
+ expect(oc.Orders.Patch).toHaveBeenCalledWith(order.ID, {ShippingAddressID: order.ShippingAddressID});
+ });
+ it('should broadcast "OC:OrderShipAddressUpdates"', function() {
+ expect(rootScope.$broadcast).toHaveBeenCalledWith('OC:OrderShipAddressUpdated', order);
+ });
+ it('should call getShippingRates', function() {
+ expect(shippingRates.GetRates).toHaveBeenCalledWith(order);
+ });
+ });
+
+ describe('initShippingRates', function() {
+ beforeEach(function() {
+ checkoutShippingController.initShippingRates(order);
+ scope.$digest();
+ });
+ it('should call getShippingRates', function() {
+ expect(shippingRates.GetRates).toHaveBeenCalledWith(order);
+ });
+ });
+
+ describe('getShippingRates', function() {
+ beforeEach(function() {
+ checkoutShippingController.getShippingRates(order);
+ scope.$digest();
+ });
+ it('should call ShippingRates GetRates method', function() {
+ expect(shippingRates.GetRates).toHaveBeenCalledWith(order);
+ });
+ it('should call analyzeShipments', function() {
+ expect(shippingRates.AnalyzeShipments).toHaveBeenCalledWith(order, rates.Shipments);
+ });
+ });
+
+ describe('shipperSelected', function() {
+ beforeEach(function() {
+ checkoutShippingController.shippingRates = rates.Shipments;
+ checkoutShippingController.shipperSelected(order);
+ scope.$digest();
+ });
+ it('should call ShippingRates ManageShipments method', function() {
+ expect(shippingRates.ManageShipments).toHaveBeenCalledWith(order, rates.Shipments);
+ });
+ it('should broadcast "OC:UpdateOrder"', function() {
+ expect(rootScope.$broadcast).toHaveBeenCalledWith('OC:UpdateOrder', order.ID);
+ });
+ });
+ });
+
+ describe('Factory: ShippingRates', function() {
+ var resource, apiurl, shippingratesurl, shippingRates, httpBackend;
+ beforeEach(inject(function($resource, _apiurl_, ShippingRates, $httpBackend) {
+ resource = $resource;
+ apiurl = _apiurl_;
+ shippingRates = ShippingRates;
+ httpBackend = $httpBackend;
+ shippingratesurl = apiurl + '/v1/integrationproxy/shippingrates';
+ var defer = q.defer();
+ defer.resolve();
+ }));
+
+ it('should have a GetRates method', function() {
+ expect(typeof shippingRates.GetRates).toBe('function');
+ });
+
+ it('should have a GetLineItemRates method', function() {
+ expect(typeof shippingRates.GetLineItemRates).toBe('function');
+ });
+
+ it('should have a SetShippingCost method', function() {
+ expect(typeof shippingRates.SetShippingCost).toBe('function');
+ });
+
+ it('should have a ManageShipments method', function() {
+ expect(typeof shippingRates.ManageShipments).toBe('function');
+ });
+
+ it('should have a AnalyzeShipments method', function() {
+ expect(typeof shippingRates.AnalyzeShipments).toBe('function');
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/app/checkout/tests/checkout.spec.js b/src/app/checkout/tests/checkout.spec.js
new file mode 100644
index 00000000..6e15e9c3
--- /dev/null
+++ b/src/app/checkout/tests/checkout.spec.js
@@ -0,0 +1,297 @@
+describe('Component: Checkout', function() {
+ var scope,
+ q,
+ oc,
+ lineItemHelpers,
+ order,
+ currentOrder,
+ submittedOrder,
+ lineItemsList,
+ paymentListEmpty,
+ paymentListItems,
+ orderPayments,
+ orderPromotions,
+ address;
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(module(function($provide) {
+ $provide.value('CurrentOrder', {
+ ID: 'CurrentOrder123',
+ BillingAddressID: 'TestAddress123456789',
+ ShippingAddressID: 'TestAddress123456789',
+ BillingAddress: {
+ ID: 'TestAddress123456789'
+ }
+ });
+ $provide.value('OrderPayments', {
+ Items: [{Type: 'CreditCard', CreditCardID: 'CC123'}, {Type: 'SpendingAccount', SpendingAccountID: 'SA123'}],
+ Meta: {
+ Page: 1,
+ PageSize: 20,
+ TotalCount: 0,
+ TotalPages: 1,
+ ItemRange: [1, 0]
+ }
+ });
+ $provide.value('SubmittedOrder', {
+ ID: 'SubmittedOrder123',
+ BillingAddressID: 'TestAddress123456789',
+ ShippingAddressID: 'TestAddress123456789',
+ BillingAddress: {
+ ID: 'TestAddress123456789'
+ }
+ });
+ }));
+ beforeEach(inject(function($q, $rootScope, OrderCloud, ocLineItems, CurrentOrder, OrderPayments, SubmittedOrder) {
+ q = $q;
+ oc = OrderCloud;
+ lineItemHelpers = ocLineItems;
+ scope = $rootScope.$new();
+ currentOrder = CurrentOrder;
+ submittedOrder = SubmittedOrder;
+ orderPayments = OrderPayments;
+ order = {
+ ID: 'TestOrder123456789',
+ Type: 'Standard',
+ FromUserID: 'TestUser123456789',
+ BillingAddressID: 'TestAddress123456789',
+ BillingAddress: {
+ ID: 'TestAddress123456789'
+ },
+ ShippingAddressID: 'TestAddress123456789',
+ Comments: null,
+ ShippingCost: null,
+ TaxCost: null,
+ Subtotal: 10,
+ Total: 10
+ };
+ lineItemsList = {
+ Items : [{ID: '1'}, {ID: '2'}],
+ Meta : {
+ Page: 1,
+ PageSize: 20,
+ TotalCount: 2,
+ TotalPages: 1,
+ ItemRange: [1,2]
+ }
+ };
+ paymentListEmpty = {
+ Items: [],
+ Meta: {
+ Page: 1,
+ PageSize: 20,
+ TotalCount: 0,
+ TotalPages: 1,
+ ItemRange: [1, 0]
+ }
+ };
+ paymentListItems = {
+ Items: [{Type: 'CreditCard', CreditCardID: 'CC123', Amount: 5}, {Type: 'SpendingAccount', SpendingAccountID: 'SA123', Amount: 5}],
+ Meta: {
+ Page: 1,
+ PageSize: 20,
+ TotalCount: 0,
+ TotalPages: 1,
+ ItemRange: [1, 0]
+ }
+ };
+ orderPromotions = {
+ Items: [],
+ Meta: {
+ Page: 1,
+ PageSize: 20,
+ TotalCount: 0,
+ TotalPages: 1,
+ ItemRange: [1, 0]
+ }
+ };
+ address = {
+ ID: 'TestAddress123456789'
+ };
+ }));
+
+ describe('State: checkout', function() {
+ var state;
+ beforeEach(inject(function($state) {
+ state = $state.get('checkout');
+ var defer = q.defer();
+ defer.resolve();
+ spyOn(oc.Me, 'GetAddress').and.returnValue(defer.promise);
+ spyOn(oc.Orders, 'ListPromotions').and.returnValue(defer.promise);
+
+ var paymentsDefer = q.defer();
+ paymentsDefer.resolve(paymentListEmpty);
+ spyOn(oc.Payments, 'List').and.returnValue(paymentsDefer.promise);
+ spyOn(oc.Payments, 'Create').and.returnValue(paymentsDefer.promise);
+ }));
+ it('should call Me.GetAddress for ShippingAddressID', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderShipAddress);
+ expect(oc.Me.GetAddress).toHaveBeenCalledWith(currentOrder.ShippingAddressID);
+ }));
+ it('should call Orders.ListPromotions', inject(function($injector) {
+ $injector.invoke(state.resolve.CurrentPromotions);
+ expect(oc.Orders.ListPromotions).toHaveBeenCalledWith(currentOrder.ID);
+ }));
+ it('should call Me.GetAddress for BillingAddressID', inject(function($injector) {
+ $injector.invoke(state.resolve.OrderBillingAddress);
+ expect(oc.Me.GetAddress).toHaveBeenCalledWith(currentOrder.BillingAddressID);
+ }));
+ });
+
+ describe('Controller: CheckoutController', function() {
+ var checkoutController, toaster;
+ beforeEach(inject(function($state, $controller, toastr) {
+ toaster = toastr;
+ state = $state;
+ checkoutController = $controller('CheckoutCtrl', {
+ $scope: scope,
+ OrderShipAddress: address,
+ OrderBillingAddress: address,
+ CurrentPromotions: orderPromotions,
+ OrderPayments: paymentListItems
+ });
+ var orderDefer = q.defer();
+ orderDefer.resolve(order);
+ spyOn(oc.Orders, 'Submit').and.returnValue(orderDefer.promise);
+
+ var defer = q.defer();
+ defer.resolve(order);
+ spyOn(toaster, 'success');
+ spyOn($state, 'go').and.returnValue(true);
+ spyOn($state, 'transitionTo').and.returnValue(true);
+
+ var addressDefer = q.defer();
+ addressDefer.resolve(address);
+
+ spyOn(oc.Me, 'GetAddress').and.returnValue(addressDefer.promise);
+
+ spyOn(oc.Payments, 'List').and.returnValue(defer.promise);
+
+ spyOn(oc.Orders, 'RemovePromotion').and.returnValue(defer.promise);
+
+ spyOn(oc.Orders, 'ListPromotions').and.returnValue(defer.promise);
+ }));
+
+ describe('submitOrder', function() {
+ beforeEach(function() {
+ checkoutController.submitOrder(order);
+ scope.$digest();
+ });
+ it('should call Orders Submit method', function(){
+ expect(oc.Orders.Submit).toHaveBeenCalledWith(order.ID);
+ });
+ it('should go to confirmation state', function() {
+ expect(state.go).toHaveBeenCalledWith('confirmation', {orderid: order.ID}, {reload: 'base'});
+ });
+ it('should call toastr when successful', function(){
+ expect(toaster.success).toHaveBeenCalled();
+ });
+ });
+
+ describe('OC:OrderShipAddressUpdated', function() {
+ it('should call Me GetAddress method on broadcasted order ShippingAddressID', inject(function($rootScope) {
+ $rootScope.$broadcast('OC:OrderShipAddressUpdated', order);
+ scope.$digest();
+ expect(oc.Me.GetAddress).toHaveBeenCalledWith(order.ShippingAddressID);
+ }));
+ it('should update checkoutController.shippingAddress to new address', inject(function($rootScope) {
+ checkoutController.shippingAddress = {ID: 'SA123'};
+ expect(checkoutController.shippingAddress.ID).toEqual('SA123');
+ $rootScope.$broadcast('OC:OrderShipAddressUpdated', order);
+ scope.$digest();
+ expect(checkoutController.shippingAddress.ID).toEqual('TestAddress123456789');
+ }));
+ });
+
+ describe('OC:OrderBillAddressUpdated', function() {
+ it('should call Me GetAddress method on broadcasted order BillingAddressID', inject(function($rootScope) {
+ $rootScope.$broadcast('OC:OrderBillAddressUpdated', order);
+ scope.$digest();
+ expect(oc.Me.GetAddress).toHaveBeenCalledWith(order.BillingAddressID);
+ }));
+ it('should update checkoutController.billingAddress to new address', inject(function($rootScope) {
+ checkoutController.billingAddress = {ID: 'BA123'};
+ expect(checkoutController.billingAddress.ID).toEqual('BA123');
+ $rootScope.$broadcast('OC:OrderBillAddressUpdated', order);
+ scope.$digest();
+ expect(checkoutController.billingAddress.ID).toEqual('TestAddress123456789');
+ }));
+ });
+
+ describe('removePromotion', function() {
+ it('should call Orders RemovePromotion method', function() {
+ checkoutController.removePromotion(order, {Code: 'Promo123'});
+ scope.$digest();
+ expect(oc.Orders.RemovePromotion).toHaveBeenCalledWith(order.ID, 'Promo123');
+ });
+ });
+
+ describe('OC:UpdatePromotions', function() {
+ it('should call Orders ListPromotions method on broadcasted orderid', inject(function($rootScope) {
+ $rootScope.$broadcast('OC:UpdatePromotions', order.ID);
+ scope.$digest();
+ expect(oc.Orders.ListPromotions).toHaveBeenCalledWith(order.ID);
+ }));
+ });
+ });
+
+ describe('Factory: AddressSelectModal', function() {
+ var addressSelectModal, uibModal;
+ beforeEach(inject(function($uibModal, _AddressSelectModal_) {
+ uibModal = $uibModal;
+ addressSelectModal = _AddressSelectModal_;
+ var defer = q.defer();
+ defer.resolve();
+ spyOn(uibModal, 'open').and.returnValue(defer.promise);
+ }));
+
+ it('should have an Open method', function() {
+ expect(typeof addressSelectModal.Open).toBe('function');
+ });
+
+ it('should call $uibModal.open when Open method is called', function() {
+ addressSelectModal.Open();
+ scope.$digest();
+ expect(uibModal.open).toHaveBeenCalled();
+ });
+ });
+
+ describe('Controller: AddressSelectCtrl', function() {
+ var addressSelectCtrl, uibModalInstance;
+ beforeEach(inject(function($controller) {
+ uibModalInstance = {close: function() {}, dismiss: function() {}};;
+ addressSelectCtrl = $controller('AddressSelectCtrl', {
+ $scope: scope,
+ $uibModalInstance: uibModalInstance,
+ Addresses: {Items: [], Meta: {}}
+ });
+ spyOn(uibModalInstance, 'close').and.returnValue();
+ spyOn(uibModalInstance, 'dismiss').and.returnValue();
+ }));
+
+ describe('select', function() {
+ it('should call $uibmodalInstance.close with selected address', function() {
+ addressSelectCtrl.select(address);
+ scope.$digest();
+ expect(uibModalInstance.close).toHaveBeenCalledWith(address);
+ });
+ });
+
+ describe('createAddress', function() {
+ it('should call $uibmodalInstance.close with "create"', function() {
+ addressSelectCtrl.createAddress();
+ scope.$digest();
+ expect(uibModalInstance.close).toHaveBeenCalledWith('create');
+ });
+ });
+
+ describe('cancel', function() {
+ it('should call $uibmodalInstance.dismiss', function() {
+ addressSelectCtrl.cancel();
+ scope.$digest();
+ expect(uibModalInstance.dismiss).toHaveBeenCalled();
+ });
+ });
+ });
+});
+
diff --git a/src/app/checkout/tests/checkout.test.js b/src/app/checkout/tests/checkout.test.js
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/checkout/tests/payments.spec.js b/src/app/checkout/tests/payments.spec.js
new file mode 100644
index 00000000..5989cf7b
--- /dev/null
+++ b/src/app/checkout/tests/payments.spec.js
@@ -0,0 +1,473 @@
+describe('Payment Directives', function() {
+ var oc,
+ q,
+ scope,
+ rootScope;
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(inject(function($q, $rootScope, OrderCloud) {
+ oc = OrderCloud;
+ q = $q;
+ scope = $rootScope.$new();
+ rootScope = $rootScope;
+ }));
+
+ describe('Controller: PaymentPurchaseOrderCtrl', function() {
+ var paymentPOCtrl,
+ order = {ID:'ORDER_ID'},
+ payment = {ID:'PAYMENT_ID', Type:'OtherPaymentType'},
+ controller;
+
+ beforeEach(inject(function($controller) {
+ controller = $controller;
+ }));
+
+ describe('Initalize payment details', function() {
+ describe('When a payment is not passed through', function() {
+ beforeEach(function() {
+ scope.payment = undefined;
+ scope.order = order;
+ });
+ it ('should list payments', function() {
+ spyOn(oc.Payments, 'List').and.callThrough();
+ paymentPOCtrl = controller('PaymentPurchaseOrderCtrl', {
+ $scope: scope
+ });
+ expect(oc.Payments.List).toHaveBeenCalledWith(order.ID);
+ });
+ it ('should update the first existing payment to a purchase order', function() {
+ var existingpayments = q.defer();
+ existingpayments.resolve({Items: [{ID: 'EXISTING_PAYMENT_ID'}]});
+ spyOn(oc.Payments, 'List').and.returnValue(existingpayments.promise);
+
+ var df = q.defer();
+ df.resolve('UPDATED_PAYMENT');
+ spyOn(oc.Payments, 'Patch').and.returnValue(df.resolve);
+
+ paymentPOCtrl = controller('PaymentPurchaseOrderCtrl', {
+ $scope: scope
+ });
+ scope.$digest();
+ expect(oc.Payments.Patch).toHaveBeenCalledWith(order.ID, 'EXISTING_PAYMENT_ID', {
+ Type: "PurchaseOrder",
+ CreditCardID: null,
+ SpendingAccountID: null,
+ Amount: null
+ });
+ });
+ it ('should create a new payment if one does not exist', function() {
+ var nopayments = q.defer();
+ nopayments.resolve({Items:[]});
+ spyOn(oc.Payments, 'List').and.returnValue(nopayments.promise);
+
+ var df = q.defer();
+ df.resolve("NEW_PAYMENT");
+ spyOn(oc.Payments, 'Create').and.returnValue(df.resolve);
+
+ paymentPOCtrl = controller('PaymentPurchaseOrderCtrl', {
+ $scope: scope
+ });
+ scope.$digest();
+ expect(oc.Payments.Create).toHaveBeenCalledWith(order.ID, {Type: "PurchaseOrder"});
+ //scope.$digest(); TODO: make this expect statement work
+ //expect(paymentPOCtrl.payment).toBe("NEW_PAYMENT");
+ })
+ });
+ describe('When a payment is passed through', function() {
+ it ('should update payment to a purchase order', function() {
+ scope.payment = payment;
+ scope.order = order;
+
+ var defer = q.defer();
+ defer.resolve('UPDATED_PAYMENT');
+ spyOn(oc.Payments, 'Patch').and.returnValue(defer.promise);
+
+ paymentPOCtrl = controller('PaymentPurchaseOrderCtrl', {
+ $scope: scope
+ });
+
+ expect(oc.Payments.Patch).toHaveBeenCalledWith(order.ID, payment.ID, {
+ ID: payment.ID,
+ SpendingAccountID:null,
+ CreditCardID:null,
+ Type:"PurchaseOrder"
+ });
+ });
+ });
+ });
+
+ describe('$scope.updatePayment()', function() {
+ it ('Should patch the current payment to a purchase order', function() {
+ scope.payment = payment;
+ scope.order = order;
+ paymentPOCtrl = controller('PaymentPurchaseOrderCtrl', {
+ $scope: scope
+ });
+ var updatedpayment = q.defer();
+ updatedpayment.resolve();
+ spyOn(oc.Payments, 'Update').and.returnValue(updatedpayment.promise);
+ spyOn(rootScope, '$broadcast').and.callThrough();
+ scope.updatePayment();
+ expect(oc.Payments.Update).toHaveBeenCalledWith(order.ID, payment.ID, payment);
+ scope.$digest();
+ expect(rootScope.$broadcast).toHaveBeenCalledWith('OC:PaymentsUpdated');
+ })
+ })
+ });
+
+ describe('Controller: PaymentSpendingAccountCtrl', function() {
+ var paymentPOCtrl,
+ order = {ID:'ORDER_ID'},
+ payment = {ID:'PAYMENT_ID', Type:'OtherPaymentType'},
+ controller;
+
+ beforeEach(inject(function($controller) {
+ controller = $controller;
+ scope.OCPaymentSpendingAccount = {
+ $error: {},
+ $valid:true,
+ $invalid:false,
+ $setValidity:(function(error, bool){
+ scope.OCPaymentSpendingAccount.$error[error] = [];
+ scope.OCPaymentSpendingAccount.$valid = bool;
+ scope.OCPaymentSpendingAccount.$invalid = !bool;
+ return null;
+ })
+ };
+ }));
+
+ describe('Get Spending Accounts', function() {
+ it ('should list the first 100 non-redemption code spending accounts', function() {
+ scope.payment = payment;
+ scope.order = order;
+
+ var data = {
+ Items:['SpendingAccount1', 'SpendingAccount2']
+ };
+
+ var df = q.defer();
+ df.resolve(data);
+ spyOn(oc.Me, 'ListSpendingAccounts').and.returnValue(df.promise);
+
+ paymentPOCtrl = controller('PaymentSpendingAccountCtrl', {
+ $scope: scope
+ });
+
+ expect(oc.Me.ListSpendingAccounts).toHaveBeenCalledWith(null, 1, 100, null, null, {RedemptionCode: '!*', AllowAsPaymentMethod: true});
+ scope.$digest();
+ expect(scope.spendingAccounts).toBe(data.Items);
+ })
+ });
+
+ describe('Initalize payment details', function() {
+ describe('When a payment is not passed through', function() {
+ beforeEach(function() {
+ scope.payment = undefined;
+ scope.order = order;
+ });
+ it ('should list payments', function() {
+ spyOn(oc.Payments, 'List').and.callThrough();
+ paymentPOCtrl = controller('PaymentSpendingAccountCtrl', {
+ $scope: scope
+ });
+ expect(oc.Payments.List).toHaveBeenCalledWith(order.ID);
+ });
+ it ('should update the first existing payment to a spending account payment', function() {
+ var existingpayments = q.defer();
+ existingpayments.resolve({Items: [{ID: 'EXISTING_PAYMENT_ID'}]});
+ spyOn(oc.Payments, 'List').and.returnValue(existingpayments.promise);
+
+ var df = q.defer();
+ df.resolve('UPDATED_PAYMENT');
+ spyOn(oc.Payments, 'Patch').and.returnValue(df.resolve);
+
+ paymentPOCtrl = controller('PaymentSpendingAccountCtrl', {
+ $scope: scope
+ });
+ scope.$digest();
+ expect(oc.Payments.Patch).toHaveBeenCalledWith(order.ID, 'EXISTING_PAYMENT_ID', {
+ Type: "SpendingAccount",
+ xp: {
+ PONumber: null
+ },
+ CreditCardID: null,
+ SpendingAccountID: null,
+ Amount: null
+ });
+ });
+ it ('should create a new payment if one does not exist', function() {
+ var nopayments = q.defer();
+ nopayments.resolve({Items:[]});
+ spyOn(oc.Payments, 'List').and.returnValue(nopayments.promise);
+
+ var df = q.defer();
+ df.resolve("NEW_PAYMENT");
+ spyOn(oc.Payments, 'Create').and.returnValue(df.resolve);
+
+ paymentPOCtrl = controller('PaymentSpendingAccountCtrl', {
+ $scope: scope
+ });
+ scope.$digest();
+ expect(oc.Payments.Create).toHaveBeenCalledWith(order.ID, {Type: "SpendingAccount"});
+ //scope.$digest();
+ //expect(scope.payment).toBe("NEW_PAYMENT");
+ })
+ });
+ describe('When a payment is passed through', function() {
+ it ('should update payment to a spending account payment', function() {
+ scope.payment = payment;
+ scope.order = order;
+
+ paymentPOCtrl = controller('PaymentSpendingAccountCtrl', {
+ $scope: scope
+ });
+
+ expect(scope.payment.CreditCardID).toBeUndefined();
+ expect(scope.payment.xp).toBeUndefined();
+ expect(scope.showPaymentOptions).toBeTruthy();
+ });
+ });
+ });
+
+ describe('$scope.changePayment()', function() {
+ it ('should show the payment options', function() {
+ scope.payment = payment;
+ scope.order = order;
+ paymentPOCtrl = controller('PaymentSpendingAccountCtrl', {
+ $scope: scope
+ });
+ scope.changePayment();
+ expect(scope.showPaymentOptions).toBeTruthy();
+ })
+ });
+
+ describe('$scope.updatePayment()', function() {
+ beforeEach(function() {
+ scope.payment = payment;
+ scope.order = order;
+ });
+ it ('should update the current payment with a SpendingAccountID', function() {
+ paymentPOCtrl = controller('PaymentSpendingAccountCtrl', {
+ $scope: scope
+ });
+ var updatedpayment = q.defer();
+ updatedpayment.resolve();
+ spyOn(oc.Payments, 'Update').and.returnValue(updatedpayment.promise);
+ spyOn(rootScope, '$broadcast').and.callThrough();
+ scope.updatePayment({spendingAccount:{ID:"FAKE_SPENDING_ACCOUNT"}});
+ expect(oc.Payments.Update).toHaveBeenCalledWith(order.ID, payment.ID, payment);
+ scope.$digest();
+ expect(rootScope.$broadcast).toHaveBeenCalledWith('OC:PaymentsUpdated');
+ expect(scope.showPaymentOptions).toBeFalsy();
+ expect(scope.payment.SpendingAccountID).toBe("FAKE_SPENDING_ACCOUNT");
+ });
+ it ('should reset $scope.payment when the update fails', function() {
+ paymentPOCtrl = controller('PaymentSpendingAccountCtrl', {
+ $scope: scope
+ });
+ var updatedpayment = q.defer();
+ updatedpayment.reject();
+ spyOn(oc.Payments, 'Update').and.returnValue(updatedpayment.promise);
+ spyOn(rootScope, '$broadcast').and.callThrough();
+ scope.updatePayment({spendingAccount:{ID:"FAKE_SPENDING_ACCOUNT"}});
+ expect(oc.Payments.Update).toHaveBeenCalledWith(order.ID, payment.ID, payment);
+ scope.$digest();
+ expect(rootScope.$broadcast).not.toHaveBeenCalled();
+ expect(scope.payment).toBe(payment);
+ })
+ })
+ });
+
+ describe('Controller: PaymentCreditCardCtrl', function() {
+ var paymentPOCtrl,
+ order = {ID:'ORDER_ID'},
+ payment = {ID:'PAYMENT_ID', Type:'OtherPaymentType'},
+ controller;
+
+ beforeEach(inject(function($controller) {
+ controller = $controller;
+ scope.OCPaymentCreditCard = {
+ $error: {},
+ $valid:true,
+ $invalid:false,
+ $setValidity:(function(error, bool){
+ scope.OCPaymentCreditCard.$error[error] = [];
+ scope.OCPaymentCreditCard.$valid = bool;
+ scope.OCPaymentCreditCard.$invalid = !bool;
+ return null;
+ })
+ };
+ }));
+
+ describe('Get Credit Cards', function() {
+ it ('should list the first 100 credit cards assigned to the user', function() {
+ scope.payment = payment;
+ scope.order = order;
+
+ var data = {
+ Items:['CreditCard1', 'CreditCard2']
+ };
+
+ var df = q.defer();
+ df.resolve(data);
+ spyOn(oc.Me, 'ListCreditCards').and.returnValue(df.promise);
+
+ paymentPOCtrl = controller('PaymentCreditCardCtrl', {
+ $scope: scope
+ });
+
+ expect(oc.Me.ListCreditCards).toHaveBeenCalledWith(null, 1, 100, null, null, {});
+ scope.$digest();
+ expect(scope.creditCards).toBe(data.Items);
+ })
+ });
+
+ describe('Initalize payment details', function() {
+ describe('When a payment is not passed through', function() {
+ beforeEach(function() {
+ scope.payment = undefined;
+ scope.order = order;
+ });
+ it ('should list payments', function() {
+ spyOn(oc.Payments, 'List').and.callThrough();
+ paymentPOCtrl = controller('PaymentCreditCardCtrl', {
+ $scope: scope
+ });
+ expect(oc.Payments.List).toHaveBeenCalledWith(order.ID);
+ });
+ it ('should update the first existing payment to a credit card payment', function() {
+ var existingpayments = q.defer();
+ existingpayments.resolve({Items: [{ID: 'EXISTING_PAYMENT_ID'}]});
+ spyOn(oc.Payments, 'List').and.returnValue(existingpayments.promise);
+
+ var df = q.defer();
+ df.resolve('UPDATED_PAYMENT');
+ spyOn(oc.Payments, 'Patch').and.returnValue(df.resolve);
+
+ paymentPOCtrl = controller('PaymentCreditCardCtrl', {
+ $scope: scope
+ });
+ scope.$digest();
+ expect(oc.Payments.Patch).toHaveBeenCalledWith(order.ID, 'EXISTING_PAYMENT_ID', {
+ Type: "CreditCard",
+ xp: {
+ PONumber: null
+ },
+ SpendingAccountID: null,
+ Amount: null
+ });
+ });
+ it ('should create a new payment if one does not exist', function() {
+ var nopayments = q.defer();
+ nopayments.resolve({Items:[]});
+ spyOn(oc.Payments, 'List').and.returnValue(nopayments.promise);
+
+ var df = q.defer();
+ df.resolve("NEW_PAYMENT");
+ spyOn(oc.Payments, 'Create').and.returnValue(df.resolve);
+
+ paymentPOCtrl = controller('PaymentCreditCardCtrl', {
+ $scope: scope
+ });
+ scope.$digest();
+ expect(oc.Payments.Create).toHaveBeenCalledWith(order.ID, {Type: "CreditCard"});
+ })
+ });
+ describe('When a payment is passed through', function() {
+ it ('should update payment to a credit card payment', function() {
+ scope.payment = payment;
+ scope.order = order;
+
+ paymentPOCtrl = controller('PaymentCreditCardCtrl', {
+ $scope: scope
+ });
+
+ expect(scope.payment.SpendingAccountID).toBeUndefined();
+ expect(scope.payment.xp).toBeUndefined();
+ expect(scope.showPaymentOptions).toBeTruthy();
+ });
+ });
+ });
+
+ describe('$scope.changePayment()', function() {
+ it ('should show the payment options', function() {
+ scope.payment = payment;
+ scope.order = order;
+ paymentPOCtrl = controller('PaymentCreditCardCtrl', {
+ $scope: scope
+ });
+ scope.changePayment();
+ expect(scope.showPaymentOptions).toBeTruthy();
+ })
+ });
+
+ describe('$scope.updatePayment()', function() {
+ beforeEach(function() {
+ scope.payment = payment;
+ scope.order = order;
+ });
+ it ('should update the current payment with a CreditCardID', function() {
+ paymentPOCtrl = controller('PaymentCreditCardCtrl', {
+ $scope: scope
+ });
+ var updatedpayment = q.defer();
+ updatedpayment.resolve();
+ spyOn(oc.Payments, 'Update').and.returnValue(updatedpayment.promise);
+ spyOn(rootScope, '$broadcast').and.callThrough();
+ scope.updatePayment({creditCard:{ID:"FAKE_CREDIT_CARD"}});
+ expect(oc.Payments.Update).toHaveBeenCalledWith(order.ID, payment.ID, payment);
+ scope.$digest();
+ expect(rootScope.$broadcast).toHaveBeenCalledWith('OC:PaymentsUpdated');
+ expect(scope.showPaymentOptions).toBeFalsy();
+ expect(scope.payment.CreditCardID).toBe("FAKE_CREDIT_CARD");
+ });
+ it ('should reset $scope.payment when the update fails', function() {
+ paymentPOCtrl = controller('PaymentCreditCardCtrl', {
+ $scope: scope
+ });
+ var updatedpayment = q.defer();
+ updatedpayment.reject();
+ spyOn(oc.Payments, 'Update').and.returnValue(updatedpayment.promise);
+ spyOn(rootScope, '$broadcast').and.callThrough();
+ scope.updatePayment({creditCard:{ID:"FAKE_CREDIT_CARD"}});
+ expect(oc.Payments.Update).toHaveBeenCalledWith(order.ID, payment.ID, payment);
+ scope.$digest();
+ expect(rootScope.$broadcast).not.toHaveBeenCalled();
+ expect(scope.payment).toBe(payment);
+ })
+ });
+
+ describe('$scope.createCreditCard()', function() {
+ var myPaymentCreditCardModal;
+ beforeEach(inject(function(MyPaymentCreditCardModal) {
+ myPaymentCreditCardModal = MyPaymentCreditCardModal;
+ scope.payment = payment;
+ scope.order = order;
+ scope.creditCards = [];
+
+ paymentPOCtrl = controller('PaymentCreditCardCtrl', {
+ $scope: scope,
+ MyPaymentCreditCardModal: myPaymentCreditCardModal
+ });
+ var df = q.defer();
+ df.resolve("NEW_CREDIT_CARD");
+ spyOn(myPaymentCreditCardModal, 'Create').and.returnValue(df.promise);
+ }));
+ it ('should call MyPaymentCreditCardModal.Create()', function() {
+ scope.createCreditCard();
+ expect(myPaymentCreditCardModal.Create).toHaveBeenCalled();
+ });
+ it ('should add the new credit card to $scope.creditCards', function() {
+ scope.createCreditCard();
+ scope.$digest();
+ expect(scope.creditCards).toEqual(["NEW_CREDIT_CARD"]);
+ });
+ it ('should call $scope.updatePayment with the new credit card', function() {
+ spyOn(scope, 'updatePayment').and.callThrough();
+ scope.createCreditCard();
+ scope.$digest();
+ expect(scope.updatePayment).toHaveBeenCalledWith({creditCard:"NEW_CREDIT_CARD"});
+ })
+ })
+ });
+});
\ No newline at end of file
diff --git a/src/app/common/config/angular-busy.config.js b/src/app/common/config/angular-busy.config.js
new file mode 100644
index 00000000..a26a380c
--- /dev/null
+++ b/src/app/common/config/angular-busy.config.js
@@ -0,0 +1,9 @@
+angular.module('orderCloud')
+ .config(function(angularBusyDefaults) {
+ angular.extend(angularBusyDefaults, {
+ templateUrl:'common/templates/view.loading.tpl.html',
+ message:null,
+ wrapperClass: 'indicator-container'
+ })
+ })
+;
\ No newline at end of file
diff --git a/src/app/common/config/localforage.config.js b/src/app/common/config/localforage.config.js
new file mode 100644
index 00000000..1f49ed07
--- /dev/null
+++ b/src/app/common/config/localforage.config.js
@@ -0,0 +1,12 @@
+angular.module('orderCloud')
+ .config(LocalForage)
+;
+
+function LocalForage($localForageProvider) {
+ $localForageProvider.config({
+ version: 1.0,
+ name: 'OrderCloud',
+ storeName: 'four51',
+ description: 'Four51 OrderCloud Local Storage'
+ });
+}
\ No newline at end of file
diff --git a/src/app/common/directives/confirm-password.js b/src/app/common/directives/confirm-password.js
new file mode 100644
index 00000000..29a7f89d
--- /dev/null
+++ b/src/app/common/directives/confirm-password.js
@@ -0,0 +1,32 @@
+angular.module('orderCloud')
+ .directive('confirmpassword', confirmpassword)
+;
+//TODO: remove this directive in favor of ui.validate
+function confirmpassword() {
+ return {
+ restrict: 'A',
+ require: 'ngModel',
+ link: function (scope, element, attrs, ngModel) {
+ if (!ngModel) return;
+
+ //watch own value and re-validate on change
+ scope.$watch(attrs.ngModel, function() {
+ validate();
+ });
+
+ //watch other value and re-validate on change
+ attrs.$observe('confirmpassword', function(val) {
+ validate();
+ });
+
+ var validate = function() {
+ var val1 = ngModel.$viewValue;
+ var val2 = attrs.confirmpassword;
+
+ (!val1 || !val2 || val1 === val2) ? ngModel.$setValidity('confirmpassword', true) : ngModel.$setValidity('confirmpassword', false);
+ };
+ }
+ }
+}
+
+
diff --git a/src/app/common/directives/oc-quantity-input.js b/src/app/common/directives/oc-quantity-input.js
new file mode 100644
index 00000000..56945795
--- /dev/null
+++ b/src/app/common/directives/oc-quantity-input.js
@@ -0,0 +1,45 @@
+angular.module('orderCloud')
+ .directive('ocQuantityInput', OCQuantityInput)
+
+;
+
+function OCQuantityInput(toastr, OrderCloud, $rootScope) {
+ return {
+ scope: {
+ product: '=',
+ lineitem: '=',
+ label: '@',
+ order: '=',
+ onUpdate: '&'
+ },
+ templateUrl: 'common/templates/quantityInput.tpl.html',
+ replace: true,
+ link: function (scope) {
+ if (scope.product){
+ scope.item = scope.product;
+ scope.content = "product"
+ }
+ else if(scope.lineitem){
+ scope.item = scope.lineitem;
+ scope.content = "lineitem";
+ scope.updateQuantity = function() {
+ if (scope.item.Quantity > 0) {
+ OrderCloud.LineItems.Patch(scope.order.ID, scope.item.ID, {Quantity: scope.item.Quantity})
+ .then(function (data) {
+ data.Product = scope.lineitem.Product;
+ scope.item = data;
+ scope.lineitem = data;
+ if (typeof scope.onUpdate === "function") scope.onUpdate(scope.lineitem);
+ toastr.success('Quantity Updated');
+ $rootScope.$broadcast('OC:UpdateOrder', scope.order.ID, 'Calculating Order Total');
+ });
+ }
+ }
+ }
+ else{
+ toastr.error('Please input either a product or lineitem attribute in the directive','Error');
+ console.error('Please input either a product or lineitem attribute in the quantityInput directive ')
+ }
+ }
+ }
+}
diff --git a/src/app/common/filters/fa-creditcard.js b/src/app/common/filters/fa-creditcard.js
new file mode 100644
index 00000000..cfdd838a
--- /dev/null
+++ b/src/app/common/filters/fa-creditcard.js
@@ -0,0 +1,32 @@
+angular.module('orderCloud')
+ .filter('faCreditCard', faCreditCard)
+;
+
+function faCreditCard() {
+ return function(type) {
+ var result = 'fa-credit-card-alt';
+ switch(type.toLowerCase()) {
+ case 'visa':
+ result = 'fa-cc-visa';
+ break;
+ case 'mastercard':
+ result = 'fa-cc-mastercard';
+ break;
+ case 'american express':
+ result = 'fa-cc-amex';
+ break;
+ case 'diners club':
+ result = 'fa-cc-diners-club';
+ break;
+ case 'discover':
+ result = 'fa-cc-discover';
+ break;
+ case 'jcb':
+ result = 'fa-cc-jcb';
+ break;
+ default:
+ result = 'fa-credit-card-alt';
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/app/common/filters/humanize.js b/src/app/common/filters/humanize.js
new file mode 100644
index 00000000..510c9846
--- /dev/null
+++ b/src/app/common/filters/humanize.js
@@ -0,0 +1,14 @@
+angular.module('orderCloud')
+ .filter('humanize', humanize)
+;
+
+function humanize() {
+ return function(string) {
+ if (!string) return;
+
+ return string
+ .replace(/([A-Z])/g, ' $1')
+ .replace(/^./, function(str){ return str.toUpperCase(); })
+ .trim();
+ }
+}
\ No newline at end of file
diff --git a/src/app/common/filters/oc-address.js b/src/app/common/filters/oc-address.js
new file mode 100644
index 00000000..d386c6d5
--- /dev/null
+++ b/src/app/common/filters/oc-address.js
@@ -0,0 +1,39 @@
+angular.module('orderCloud')
+ .filter('address', AddressFilter)
+;
+
+function AddressFilter() {
+ return function(address, option) {
+ if (!address) return null;
+ if (option === 'full') {
+ var result = [];
+
+ //address name
+ if (address.AddressName) result.push('' + address.AddressName + ' ');
+
+ //address first/last
+ if (address.FirstName || address.LastName) {
+ result.push((address.FirstName && !address.LastName) ? address.FirstName : ((!address.FirstName && address.LastName) ? address.LastName : (address.FirstName + ' ' + address.LastName)));
+ }
+
+ //company name
+ if (address.CompanyName) result.push(address.CompanyName);
+
+ //street 1 (required)
+ result.push(address.Street1);
+
+ //street 1 (optional)
+ if (address.Street2) result.push(address.Street2);
+
+ //city, state zip
+ result.push(address.City + ', ' + address.State + ' ' + address.Zip);
+
+ if (address.Phone) result.push(address.Phone);
+
+ return result.join(' ');
+ }
+ else {
+ return address.Street1 + (address.Street2 ? ', ' + address.Street2 : '');
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/app/common/services/oc-authnet.js b/src/app/common/services/oc-authnet.js
new file mode 100644
index 00000000..aea453e3
--- /dev/null
+++ b/src/app/common/services/oc-authnet.js
@@ -0,0 +1,82 @@
+angular.module('orderCloud')
+ .factory('ocAuthNet', AuthorizeNet)
+;
+
+function AuthorizeNet( $q, $resource, OrderCloud) {
+ return {
+ 'CreateCreditCard': _createCreateCard,
+ 'UpdateCreditCard': _updateCreditCard,
+ 'DeleteCreditCard' : _deleteCreditCard
+ // 'AuthAndCapture' : _authAndCapture
+
+ };
+
+ function _createCreateCard(creditCard, buyerID) {
+ var year = creditCard.ExpirationYear.toString().substring(2,4);
+ var ExpirationDate = creditCard.ExpirationMonth.concat(year);
+
+ return makeApiCall('POST',{
+ 'buyerID' : buyerID ? buyerID : OrderCloud.BuyerID.Get(),
+ 'TransactionType' : "createCreditCard",
+ 'CardDetails' : {
+ 'CardholderName' : creditCard.CardholderName,
+ 'CardType' : creditCard.CardType,
+ 'CardNumber' : creditCard.CardNumber,
+ 'ExpirationDate' : ExpirationDate,
+ 'CardCode' : creditCard.CardCode
+ }
+ });
+ }
+
+ function _updateCreditCard(creditCard, buyerID) {
+ var year = creditCard.ExpirationYear.toString().substring(2,4);
+ var ExpirationDate = creditCard.ExpirationMonth.concat(year);
+
+ return makeApiCall('POST',{
+ 'buyerID' : buyerID ? buyerID : OrderCloud.BuyerID.Get(),
+ 'TransactionType' : "updateCreditCard",
+ 'CardDetails' : {
+ 'CreditCardID' : creditCard.ID,
+ 'CardholderName' : creditCard.CardholderName,
+ 'CardType' : creditCard.CardType,
+ 'CardNumber' : 'XXXX'+ creditCard.PartialAccountNumber,
+ 'ExpirationDate' : ExpirationDate
+ }
+ });
+
+ }
+ function _deleteCreditCard(creditCard, buyerID) {
+ return makeApiCall('POST', {
+ 'buyerID': buyerID ? buyerID : OrderCloud.BuyerID.Get(),
+ 'TransactionType': "deleteCreditCard",
+ 'CardDetails': {
+ 'CreditCardID': creditCard.ID
+ }
+ });
+
+ }
+ // function _authAndCapture() {
+ //
+ // }
+
+
+ function makeApiCall(method, requestBody) {
+ var apiUrl = 'https://api.ordercloud.io/v1/integrationproxy/authorizenettest';
+ var d = $q.defer();
+ $resource(apiUrl, null, {
+ callApi: {
+ method: method,
+ headers: {
+ 'Authorization': 'Bearer ' + OrderCloud.Auth.ReadToken()
+ }
+ }
+ }).callApi(requestBody).$promise
+ .then(function(data) {
+ d.resolve(data);
+ })
+ .catch(function(ex) {
+ d.reject(ex);
+ });
+ return d.promise;
+ }
+}
\ No newline at end of file
diff --git a/src/app/common/services/oc-confirm.js b/src/app/common/services/oc-confirm.js
new file mode 100644
index 00000000..d0d113ff
--- /dev/null
+++ b/src/app/common/services/oc-confirm.js
@@ -0,0 +1,41 @@
+angular.module('orderCloud')
+ .factory('ocConfirm', OrderCloudConfirmService)
+ .controller('ConfirmModalCtrl', ConfirmModalController)
+;
+
+function OrderCloudConfirmService($uibModal) {
+ var service = {
+ Confirm: _confirm
+ };
+
+ function _confirm(message) {
+ return $uibModal.open({
+ animation:false,
+ backdrop:'static',
+ templateUrl: 'common/templates/confirm.modal.tpl.html',
+ controller: 'ConfirmModalCtrl',
+ controllerAs: 'confirmModal',
+ size: 'sm',
+ resolve: {
+ ConfirmMessage: function() {
+ return message;
+ }
+ }
+ }).result
+ }
+
+ return service;
+}
+
+function ConfirmModalController($uibModalInstance, ConfirmMessage) {
+ var vm = this;
+ vm.message = ConfirmMessage;
+
+ vm.confirm = function() {
+ $uibModalInstance.close();
+ };
+
+ vm.cancel = function() {
+ $uibModalInstance.dismiss();
+ };
+}
\ No newline at end of file
diff --git a/src/app/common/services/oc-creditcard-utility.js b/src/app/common/services/oc-creditcard-utility.js
new file mode 100644
index 00000000..36a90833
--- /dev/null
+++ b/src/app/common/services/oc-creditcard-utility.js
@@ -0,0 +1,70 @@
+angular.module('orderCloud')
+ .factory('ocCreditCardUtility', CreditCardUtility)
+;
+
+
+function CreditCardUtility() {
+ //return the expirationMonth array and its function
+ var creditCardUtility = {
+ ExpirationMonths: [{
+ number: 1,
+ string: '01'
+ }, {
+ number: 2,
+ string: '02'
+ }, {
+ number: 3,
+ string: '03'
+ }, {
+ number: 4,
+ string: '04'
+ }, {
+ number: 5,
+ string: '05'
+ }, {
+ number: 6,
+ string: '06'
+ }, {
+ number: 7,
+ string: '07'
+ }, {
+ number: 8,
+ string: '08'
+ }, {
+ number: 9,
+ string: '09'
+ }, {
+ number: 10,
+ string: '10'
+ }, {
+ number: 11,
+ string: '11'
+ }, {
+ number: 12,
+ string: '12'
+ }],
+ ExpirationYears: [],
+ isLeapYear: function leapYear(year) {
+ return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
+ },
+ CreditCardTypes : [
+ 'MasterCard',
+ 'American Express',
+ 'Discover',
+ 'Visa'
+ ]
+ };
+
+ function _ccExpireYears() {
+ var today = new Date();
+ today = today.getFullYear();
+ for (var x = today; x < today + 21; x++) {
+ creditCardUtility.ExpirationYears.push(x);
+ }
+ return creditCardUtility.ExpirationYears;
+ }
+
+ _ccExpireYears();
+
+ return creditCardUtility;
+}
diff --git a/src/app/common/services/oc-geography.js b/src/app/common/services/oc-geography.js
new file mode 100644
index 00000000..4055f656
--- /dev/null
+++ b/src/app/common/services/oc-geography.js
@@ -0,0 +1,347 @@
+angular.module('orderCloud')
+ .factory('ocGeography', OCGeography)
+;
+
+function OCGeography() {
+ var _countries = [
+ { "label": "United States of America", "value": "US"},
+ { "label": "Afghanistan", "value": "AF"},
+ { "label": "Åland Islands", "value": "AX"},
+ { "label": "Albania", "value": "AL"},
+ { "label": "Algeria", "value": "DZ"},
+ { "label": "American Samoa", "value": "AS"},
+ { "label": "Andorra", "value": "AD"},
+ { "label": "Angola", "value": "AO"},
+ { "label": "Anguilla", "value": "AI"},
+ { "label": "Antarctica", "value": "AQ"},
+ { "label": "Antigua and Barbuda", "value": "AG"},
+ { "label": "Argentina", "value": "AR"},
+ { "label": "Armenia", "value": "AM"},
+ { "label": "Aruba", "value": "AW"},
+ { "label": "Australia", "value": "AU"},
+ { "label": "Austria", "value": "AT"},
+ { "label": "Azerbaijan", "value": "AZ"},
+ { "label": "Bahamas", "value": "BS"},
+ { "label": "Bahrain", "value": "BH"},
+ { "label": "Bangladesh", "value": "BD"},
+ { "label": "Barbados", "value": "BB"},
+ { "label": "Belarus", "value": "BY"},
+ { "label": "Belgium", "value": "BE"},
+ { "label": "Belize", "value": "BZ"},
+ { "label": "Benin", "value": "BJ"},
+ { "label": "Bermuda", "value": "BM"},
+ { "label": "Bhutan", "value": "BT"},
+ { "label": "Bolivia", "value": "BO"},
+ { "label": "Bosnia and Herzegovina", "value": "BA"},
+ { "label": "Botswana", "value": "BW"},
+ { "label": "Bouvet Island", "value": "BV"},
+ { "label": "Brazil", "value": "BR"},
+ { "label": "British Indian Ocean Territory", "value": "IO"},
+ { "label": "Brunei Darussalam", "value": "BN"},
+ { "label": "Bulgaria", "value": "BG"},
+ { "label": "Burkina Faso", "value": "BF"},
+ { "label": "Burundi", "value": "BI"},
+ { "label": "Cambodia", "value": "KH"},
+ { "label": "Cameroon", "value": "CM"},
+ { "label": "Canada", "value": "CA"},
+ { "label": "Cape Verde", "value": "CV"},
+ { "label": "Cayman Islands", "value": "KY"},
+ { "label": "Central African Republic", "value": "CF"},
+ { "label": "Chad", "value": "TD"},
+ { "label": "Chile", "value": "CL"},
+ { "label": "China", "value": "CN"},
+ { "label": "Christmas Island Australia", "value": "CX"},
+ { "label": "Cocos Keeling Islands", "value": "CC"},
+ { "label": "Colombia", "value": "CO"},
+ { "label": "Comoros", "value": "KM"},
+ { "label": "Congo", "value": "CG"},
+ { "label": "Congo, D.R.", "value": "CD"},
+ { "label": "Cook Islands", "value": "CK"},
+ { "label": "Costa Rica", "value": "CR"},
+ { "label": "Cote D'Ivoire Ivory Coast", "value": "CI"},
+ { "label": "Croatia Hrvatska", "value": "HR"},
+ { "label": "Cuba", "value": "CU"},
+ { "label": "Cyprus", "value": "CY"},
+ { "label": "Czech Republic", "value": "CZ"},
+ { "label": "Denmark", "value": "DK"},
+ { "label": "Djibouti", "value": "DJ"},
+ { "label": "Dominica", "value": "DM"},
+ { "label": "Dominican Republic", "value": "DO"},
+ { "label": "Ecuador", "value": "EC"},
+ { "label": "Egypt", "value": "EG"},
+ { "label": "El Salvador", "value": "SV"},
+ { "label": "Equatorial Guinea", "value": "GQ"},
+ { "label": "Eritrea", "value": "ER"},
+ { "label": "Estonia", "value": "EE"},
+ { "label": "Ethiopia", "value": "ET"},
+ { "label": "Faeroe Islands", "value": "FO"},
+ { "label": "Falkland Islands Malvinas", "value": "FK"},
+ { "label": "Fiji", "value": "FJ"},
+ { "label": "Finland", "value": "FI"},
+ { "label": "France", "value": "FR"},
+ { "label": "France, Metropolitan", "value": "FX"},
+ { "label": "French Guiana", "value": "GF"},
+ { "label": "French Polynesia", "value": "PF"},
+ { "label": "French Southern Territories", "value": "TF"},
+ { "label": "Gabon", "value": "GA"},
+ { "label": "Gambia", "value": "GM"},
+ { "label": "Georgia", "value": "GE"},
+ { "label": "Germany", "value": "DE"},
+ { "label": "Ghana", "value": "GH"},
+ { "label": "Gibraltar", "value": "GI"},
+ { "label": "Greece", "value": "GR"},
+ { "label": "Greenland", "value": "GL"},
+ { "label": "Grenada", "value": "GD"},
+ { "label": "Guadeloupe", "value": "GP"},
+ { "label": "Guam", "value": "GU"},
+ { "label": "Guatemala", "value": "GT"},
+ { "label": "Guinea", "value": "GN"},
+ { "label": "Guinea Bissau", "value": "GW"},
+ { "label": "Guyana", "value": "GY"},
+ { "label": "Haiti", "value": "HT"},
+ { "label": "Heard and McDonald Is.", "value": "HM"},
+ { "label": "Honduras", "value": "HN"},
+ { "label": "Hong Kong", "value": "HK"},
+ { "label": "Hungary", "value": "HU"},
+ { "label": "Iceland", "value": "IS"},
+ { "label": "India", "value": "IN"},
+ { "label": "Indonesia", "value": "ID"},
+ { "label": "Iran", "value": "IR"},
+ { "label": "Iraq", "value": "IQ"},
+ { "label": "Isle of Man", "value": "IM"},
+ { "label": "Ireland", "value": "IE"},
+ { "label": "Israel", "value": "IL"},
+ { "label": "Italy", "value": "IT"},
+ { "label": "Jamaica", "value": "JM"},
+ { "label": "Japan", "value": "JP"},
+ { "label": "Jersey", "value": "JE"},
+ { "label": "Jordan", "value": "JO"},
+ { "label": "Kazakhstan", "value": "KZ"},
+ { "label": "Kenya", "value": "KE"},
+ { "label": "Kiribati", "value": "KI"},
+ { "label": "Korea North", "value": "KP"},
+ { "label": "Korea South", "value": "KR"},
+ { "label": "Kuwait", "value": "KW"},
+ { "label": "Kyrgyzstan", "value": "KG"},
+ { "label": "Lao P.Dem.R.", "value": "LA"},
+ { "label": "Latvia", "value": "LV"},
+ { "label": "Lebanon", "value": "LB"},
+ { "label": "Lesotho", "value": "LS"},
+ { "label": "Liberia", "value": "LR"},
+ { "label": "Libyan Arab Jamahiriya", "value": "LY"},
+ { "label": "Liechtenstein", "value": "LI"},
+ { "label": "Lithuania", "value": "LT"},
+ { "label": "Luxembourg", "value": "LU"},
+ { "label": "Macau", "value": "MO"},
+ { "label": "Macedonia", "value": "MK"},
+ { "label": "Madagascar", "value": "MG"},
+ { "label": "Malawi", "value": "MW"},
+ { "label": "Malaysia", "value": "MY"},
+ { "label": "Maldives", "value": "MV"},
+ { "label": "Mali", "value": "ML"},
+ { "label": "Malta", "value": "MT"},
+ { "label": "Marshall Islands", "value": "MH"},
+ { "label": "Martinique", "value": "MQ"},
+ { "label": "Mauritania", "value": "MR"},
+ { "label": "Mauritius", "value": "MU"},
+ { "label": "Mayotte", "value": "YT"},
+ { "label": "Mexico", "value": "MX"},
+ { "label": "Micronesia", "value": "FM"},
+ { "label": "Moldova", "value": "MD"},
+ { "label": "Monaco", "value": "MC"},
+ { "label": "Mongolia", "value": "MN"},
+ { "label": "Montenegro", "value": "ME"},
+ { "label": "Montserrat", "value": "MS"},
+ { "label": "Morocco", "value": "MA"},
+ { "label": "Mozambique", "value": "MZ"},
+ { "label": "Myanmar", "value": "MM"},
+ { "label": "Namibia", "value": "NA"},
+ { "label": "Nauru", "value": "NR"},
+ { "label": "Nepal", "value": "NP"},
+ { "label": "Netherlands", "value": "NL"},
+ { "label": "Netherlands Antilles", "value": "AN"},
+ { "label": "New Caledonia", "value": "NC"},
+ { "label": "New Zealand", "value": "NZ"},
+ { "label": "Nicaragua", "value": "NI"},
+ { "label": "Niger", "value": "NE"},
+ { "label": "Nigeria", "value": "NG"},
+ { "label": "Niue", "value": "NU"},
+ { "label": "Norfolk Island", "value": "NF"},
+ { "label": "Northern Mariana Islands", "value": "MP"},
+ { "label": "Norway", "value": "NO"},
+ { "label": "Oman", "value": "OM"},
+ { "label": "Pakistan", "value": "PK"},
+ { "label": "Palau", "value": "PW"},
+ { "label": "Palestinian Territory, Occupied", "value": "PS"},
+ { "label": "Panama", "value": "PA"},
+ { "label": "Papua New Guinea", "value": "PG"},
+ { "label": "Paraguay", "value": "PY"},
+ { "label": "Peru", "value": "PE"},
+ { "label": "Philippines", "value": "PH"},
+ { "label": "Pitcairn", "value": "PN"},
+ { "label": "Poland", "value": "PL"},
+ { "label": "Portugal", "value": "PT"},
+ { "label": "Puerto Rico", "value": "PR"},
+ { "label": "Qatar", "value": "QA"},
+ { "label": "Reunion", "value": "RE"},
+ { "label": "Romania", "value": "RO"},
+ { "label": "Russian Federation", "value": "RU"},
+ { "label": "Rwanda", "value": "RW"},
+ { "label": "Saint Helena", "value": "SH"},
+ { "label": "Saint Kitts and Nevis", "value": "KN"},
+ { "label": "Saint Lucia", "value": "LC"},
+ { "label": "Saint Pierre and Miquelon", "value": "PM"},
+ { "label": "Saint Vincent and the Grenadines", "value": "VC"},
+ { "label": "Samoa", "value": "WS"},
+ { "label": "San Marino", "value": "SM"},
+ { "label": "Sao Tome and Principe", "value": "ST"},
+ { "label": "Saudi Arabia", "value": "SA"},
+ { "label": "Senegal", "value": "SN"},
+ { "label": "Serbia", "value": "RS"},
+ { "label": "Seychelles", "value": "SC"},
+ { "label": "Sierra Leone", "value": "SL"},
+ { "label": "Singapore", "value": "SG"},
+ { "label": "Slovakia", "value": "SK"},
+ { "label": "Slovenia", "value": "SI"},
+ { "label": "Solomon Islands", "value": "SB"},
+ { "label": "Somalia", "value": "SO"},
+ { "label": "South Africa", "value": "ZA"},
+ { "label": "S. Georgia & S. Sandwich Is.", "value": "GS"},
+ { "label": "Spain", "value": "ES"},
+ { "label": "Sri Lanka", "value": "LK"},
+ { "label": "Sudan", "value": "SD"},
+ { "label": "Suriname", "value": "SR"},
+ { "label": "Svalbard & Jan Mayen Is.", "value": "SJ"},
+ { "label": "Swaziland", "value": "SZ"},
+ { "label": "Sweden", "value": "SE"},
+ { "label": "Switzerland", "value": "CH"},
+ { "label": "Syrian Arab Rep.", "value": "SY"},
+ { "label": "Taiwan", "value": "TW"},
+ { "label": "Tajikistan", "value": "TJ"},
+ { "label": "Tanzania", "value": "TZ"},
+ { "label": "Thailand", "value": "TH"},
+ { "label": "Timor-Leste", "value": "TG"},
+ { "label": "Togo", "value": "TG"},
+ { "label": "Tokelau", "value": "TK"},
+ { "label": "Tonga", "value": "TO"},
+ { "label": "Trinidad and Tobago", "value": "TT"},
+ { "label": "Tunisia", "value": "TN"},
+ { "label": "Turkey", "value": "TR"},
+ { "label": "Turkmenistan", "value": "TM"},
+ { "label": "Turks and Caicos Islands", "value": "TC"},
+ { "label": "Tuvalu", "value": "TU"},
+ { "label": "Uganda", "value": "UG"},
+ { "label": "Ukraine", "value": "UA"},
+ { "label": "United Kingdom", "value": "GB"},
+ { "label": "United Arab Emirates", "value": "AE"},
+ { "label": "US Minor Outlying Is.", "value": "UM"},
+ { "label": "Uruguay", "value": "UY"},
+ { "label": "Uzbekistan", "value": "UZ"},
+ { "label": "Vanuatu", "value": "VU"},
+ { "label": "Vatican City State", "value": "VC"},
+ { "label": "Venezuela", "value": "VE"},
+ { "label": "Viet Nam", "value": "VN"},
+ { "label": "Virgin Islands British", "value": "VG"},
+ { "label": "Virgin Islands US", "value": "VI"},
+ { "label": "Wallis and Futuna Islnds", "value": "WF"},
+ { "label": "Western Sahara", "value": "EH"},
+ { "label": "Yemen", "value": "YE"},
+ { "label": "Yugoslavia", "value": "YU"},
+ { "label": "Zambia", "value": "ZM"},
+ { "label": "Zimbabwe", "value": "ZW"}
+ ];
+ var _states = [
+ { "label": "Alabama", "value": "AL", "country": "US" },
+ { "label": "Alaska", "value": "AK", "country": "US" },
+ { "label": "Arizona", "value": "AZ", "country": "US" },
+ { "label": "Arkansas", "value": "AR", "country": "US" },
+ { "label": "California", "value": "CA", "country": "US" },
+ { "label": "Colorado", "value": "CO", "country": "US" },
+ { "label": "Connecticut", "value": "CT", "country": "US" },
+ { "label": "Delaware", "value": "DE", "country": "US" },
+ { "label": "District of Columbia", "value": "DC", "country": "US" },
+ { "label": "Florida", "value": "FL", "country": "US" },
+ { "label": "Georgia", "value": "GA", "country": "US" },
+ { "label": "Hawaii", "value": "HI", "country": "US" },
+ { "label": "Idaho", "value": "ID", "country": "US" },
+ { "label": "Illinois", "value": "IL", "country": "US" },
+ { "label": "Indiana", "value": "IN", "country": "US" },
+ { "label": "Iowa", "value": "IA", "country": "US" },
+ { "label": "Kansas", "value": "KS", "country": "US" },
+ { "label": "Kentucky", "value": "KY", "country": "US" },
+ { "label": "Louisiana", "value": "LA", "country": "US" },
+ { "label": "Maine", "value": "ME", "country": "US" },
+ { "label": "Maryland", "value": "MD", "country": "US" },
+ { "label": "Massachusetts", "value": "MA", "country": "US" },
+ { "label": "Michigan", "value": "MI", "country": "US" },
+ { "label": "Minnesota", "value": "MN", "country": "US" },
+ { "label": "Mississippi", "value": "MS", "country": "US" },
+ { "label": "Missouri", "value": "MO", "country": "US" },
+ { "label": "Montana", "value": "MT", "country": "US" },
+ { "label": "Nebraska", "value": "NE", "country": "US" },
+ { "label": "Nevada", "value": "NV", "country": "US" },
+ { "label": "New Hampshire", "value": "NH", "country": "US" },
+ { "label": "New Jersey", "value": "NJ", "country": "US" },
+ { "label": "New Mexico", "value": "NM", "country": "US" },
+ { "label": "New York", "value": "NY", "country": "US" },
+ { "label": "North Carolina", "value": "NC", "country": "US" },
+ { "label": "North Dakota", "value": "ND", "country": "US" },
+ { "label": "Ohio", "value": "OH", "country": "US" },
+ { "label": "Oklahoma", "value": "OK", "country": "US" },
+ { "label": "Oregon", "value": "OR", "country": "US" },
+ { "label": "Pennsylvania", "value": "PA", "country": "US" },
+ { "label": "Rhode Island", "value": "RI", "country": "US" },
+ { "label": "South Carolina", "value": "SC", "country": "US" },
+ { "label": "South Dakota", "value": "SD", "country": "US" },
+ { "label": "Tennessee", "value": "TN", "country": "US" },
+ { "label": "Texas", "value": "TX", "country": "US" },
+ { "label": "Utah", "value": "UT", "country": "US" },
+ { "label": "Vermont", "value": "VT", "country": "US" },
+ { "label": "Virginia", "value": "VA", "country": "US" },
+ { "label": "Washington", "value": "WA", "country": "US" },
+ { "label": "West Virginia", "value": "WV", "country": "US" },
+ { "label": "Wisconsin", "value": "WI", "country": "US" },
+ { "label": "Wyoming", "value": "WY", "country": "US" },
+ { "label": "Armed Forces Americas (AA)", "value": "AA", "country": "US" },
+ { "label": "Armed Forces Africa/Canada/Europe/Middle East (AE)", "value": "AE", "country": "US" },
+ { "label": "Armed Forces Pacific (AP)", "value": "AP", "country": "US" },
+ { "label": "American Samoa", "value": "AS", "country": "US" },
+ { "label": "Federated States of Micronesia", "value": "FM", "country": "US" },
+ { "label": "Guam", "value": "GU", "country": "US" },
+ { "label": "Marshall Islands", "value": "MH", "country": "US" },
+ { "label": "Northern Mariana Islands", "value": "MP", "country": "US" },
+ { "label": "Palau", "value": "PW", "country": "US" },
+ { "label": "Puerto Rico", "value": "PR", "country": "US" },
+ { "label": "Virgin Islands", "value": "VI", "country": "US" },
+ { "label": "Drenthe", "value": "Drenthe", "country": "NL" },
+ { "label": "Flevoland", "value": "Flevoland", "country": "NL" },
+ { "label": "Friesland", "value": "Friesland", "country": "NL" },
+ { "label": "Gelderland", "value": "Gelderland", "country": "NL" },
+ { "label": "Groningen", "value": "Groningen", "country": "NL" },
+ { "label": "Limburg", "value": "Limburg", "country": "NL" },
+ { "label": "Noord-Brabant", "value": "Noord-Brabant", "country": "NL" },
+ { "label": "Noord-Holland", "value": "Noord-Holland", "country": "NL" },
+ { "label": "Overijssel", "value": "Overijssel", "country": "NL" },
+ { "label": "Utrecht", "value": "Utrecht", "country": "NL" },
+ { "label": "Zeeland", "value": "Zeeland", "country": "NL" },
+ { "label": "Zuid-Holland", "value": "Zuid-Holland", "country": "NL" },
+ { "label": "Alberta", "value": "AB", "country": "CA" },
+ { "label": "British Columbia", "value": "BC", "country": "CA" },
+ { "label": "Manitoba", "value": "MB", "country": "CA" },
+ { "label": "New Brunswick", "value": "NB", "country": "CA" },
+ { "label": "Newfoundland and Labrador", "value": "NL", "country": "CA" },
+ { "label": "Northwest Territories", "value": "NT", "country": "CA" },
+ { "label": "Nova Scotia", "value": "NS", "country": "CA" },
+ { "label": "Nunavut", "value": "NU", "country": "CA" },
+ { "label": "Ontario", "value": "ON", "country": "CA" },
+ { "label": "Prince Edward Island", "value": "PE", "country": "CA" },
+ { "label": "Quebec", "value": "QC", "country": "CA" },
+ { "label": "Saskatchewan", "value": "SK", "country": "CA" },
+ { "label": "Yukon", "value": "YT", "country": "CA" }
+ ];
+
+ return {
+ Countries: _countries,
+ States: _states
+ };
+}
\ No newline at end of file
diff --git a/src/app/common/services/oc-lineitems.js b/src/app/common/services/oc-lineitems.js
new file mode 100644
index 00000000..a4ea15f3
--- /dev/null
+++ b/src/app/common/services/oc-lineitems.js
@@ -0,0 +1,137 @@
+angular.module('orderCloud')
+ .factory('ocLineItems', LineItemFactory)
+;
+
+function LineItemFactory($rootScope, $q, $uibModal, OrderCloud) {
+ return {
+ SpecConvert: _specConvert,
+ AddItem: _addItem,
+ GetProductInfo: _getProductInfo,
+ CustomShipping: _customShipping,
+ UpdateShipping: _updateShipping,
+ ListAll: _listAll
+ };
+
+ function _specConvert(specs) {
+ var results = [];
+ angular.forEach(specs, function (spec) {
+ var spec_to_push = {SpecID: spec.ID};
+ if (spec.Options.length > 0) {
+ if (spec.DefaultOptionID) {
+ spec_to_push.OptionID = spec.DefaultOptionID;
+ }
+ if (spec.OptionID) {
+ spec_to_push.OptionID = spec.OptionID;
+ }
+ if (spec.Value) {
+ spec_to_push.Value = spec.Value;
+ }
+ }
+ else {
+ spec_to_push.Value = spec.Value || spec.DefaultValue || null;
+ }
+ results.push(spec_to_push);
+ });
+ return results;
+ }
+
+ function _addItem(order, product){
+ var deferred = $q.defer();
+
+ var li = {
+ ProductID: product.ID,
+ Quantity: product.Quantity,
+ Specs: _specConvert(product.Specs)
+ };
+ li.ShippingAddressID = isSingleShipping(order) ? getSingleShippingAddressID(order) : null;
+ OrderCloud.LineItems.Create(order.ID, li)
+ .then(function(lineItem) {
+ $rootScope.$broadcast('OC:UpdateOrder', order.ID);
+ deferred.resolve();
+ })
+ .catch(function(error) {
+ deferred.reject(error);
+ });
+
+ function isSingleShipping(order) {
+ return _.pluck(order.LineItems, 'ShippingAddressID').length == 1;
+ }
+
+ function getSingleShippingAddressID(order) {
+ return order.LineItems[0].ShippingAddressID;
+ }
+
+ return deferred.promise;
+ }
+
+ function _getProductInfo(LineItems) {
+ var li = LineItems.Items || LineItems;
+ var productIDs = _.uniq(_.pluck(li, 'ProductID'));
+ var dfd = $q.defer();
+ var queue = [];
+ angular.forEach(productIDs, function (productid) {
+ queue.push(OrderCloud.Me.GetProduct(productid));
+ });
+ $q.all(queue)
+ .then(function (results) {
+ angular.forEach(li, function (item) {
+ item.Product = angular.copy(_.where(results, {ID: item.ProductID})[0]);
+ });
+ dfd.resolve(li);
+ });
+ return dfd.promise;
+ }
+
+ function _customShipping(Order, LineItem) {
+ var modalInstance = $uibModal.open({
+ animation: true,
+ templateUrl: 'common/lineitems/templates/shipping.tpl.html',
+ controller: 'LineItemModalCtrl',
+ controllerAs: 'liModal',
+ size: 'lg'
+ });
+
+ modalInstance.result
+ .then(function (address) {
+ address.ID = Math.floor(Math.random() * 1000000).toString();
+ OrderCloud.LineItems.SetShippingAddress(Order.ID, LineItem.ID, address)
+ .then(function () {
+ $rootScope.$broadcast('LineItemAddressUpdated', LineItem.ID, address);
+ });
+ });
+ }
+
+ function _updateShipping(Order, LineItem, AddressID) {
+ OrderCloud.Addresses.Get(AddressID)
+ .then(function (address) {
+ OrderCloud.LineItems.SetShippingAddress(Order.ID, LineItem.ID, address);
+ $rootScope.$broadcast('LineItemAddressUpdated', LineItem.ID, address);
+ });
+ }
+
+ function _listAll(orderID) {
+ var li;
+ var dfd = $q.defer();
+ var queue = [];
+ OrderCloud.LineItems.List(orderID, null, 1, 100)
+ .then(function (data) {
+ li = data;
+ if (data.Meta.TotalPages > data.Meta.Page) {
+ var page = data.Meta.Page;
+ while (page < data.Meta.TotalPages) {
+ page += 1;
+ queue.push(OrderCloud.LineItems.List(orderID, null, page, 100));
+ }
+ }
+ $q.all(queue)
+ .then(function (results) {
+ angular.forEach(results, function (result) {
+ li.Items = [].concat(li.Items, result.Items);
+ li.Meta = result.Meta;
+ });
+ dfd.resolve(li.Items);
+ });
+ });
+ return dfd.promise;
+ }
+}
\ No newline at end of file
diff --git a/src/app/common/services/oc-media.js b/src/app/common/services/oc-media.js
new file mode 100644
index 00000000..8e6fd1a3
--- /dev/null
+++ b/src/app/common/services/oc-media.js
@@ -0,0 +1,122 @@
+angular.module('orderCloud')
+ .factory('$ocMedia', ocMediaFactory)
+ .constant('MEDIA', MEDIA_CONSTANT)
+ .constant('MEDIA_PRIORITY', MEDIA_PRIORITY_CONSTANT)
+;
+
+function MEDIA_CONSTANT() {
+ return {
+ 'sm': '(max-width: 600px)',
+ 'gt-sm': '(min-width: 600px)',
+ 'md': '(min-width: 600px) and (max-width: 960px)',
+ 'gt-md': '(min-width: 960px)',
+ 'lg': '(min-width: 960px) and (max-width: 1200px)',
+ 'gt-lg': '(min-width: 1200px)'
+ }
+}
+
+function MEDIA_PRIORITY_CONSTANT() {
+ return [
+ 'gt-lg',
+ 'lg',
+ 'gt-md',
+ 'md',
+ 'gt-sm',
+ 'sm'
+ ];
+}
+
+function ocMediaFactory(MEDIA, MEDIA_PRIORITY, $rootScope, $window) {
+ var queries = {};
+ var mqls = {};
+ var results = {};
+ var normalizeCache = {};
+
+ $ocMedia.getResponsiveAttribute = getResponsiveAttribute;
+ $ocMedia.getQuery = getQuery;
+ $ocMedia.watchResponsiveAttributes = watchResponsiveAttributes;
+
+ return $ocMedia;
+
+ function $ocMedia(query) {
+ var validated = queries[query];
+ if (angular.isUndefined(validated)) {
+ validated = queries[query] = validate(query);
+ }
+
+ var result = results[validated];
+ if (angular.isUndefined(result)) {
+ result = add(validated);
+ }
+
+ return result;
+ }
+
+ function validate(query) {
+ return MEDIA[query] ||
+ ((query.charAt(0) !== '(') ? ('(' + query + ')') : query);
+ }
+
+ function add(query) {
+ var result = mqls[query] = $window.matchMedia(query);
+ result.addListener(onQueryChange);
+ return (results[result.media] = !!result.matches);
+ }
+
+ function onQueryChange(query) {
+ $rootScope.$evalAsync(function() {
+ results[query.media] = !!query.matches;
+ });
+ }
+
+ function getQuery(name) {
+ return mqls[name];
+ }
+
+ function getResponsiveAttribute(attrs, attrName) {
+ for (var i = 0; i < MEDIA_PRIORITY.length; i++) {
+ var mediaName = MEDIA_PRIORITY[i];
+ if (!mqls[queries[mediaName]].matches) {
+ continue;
+ }
+
+ var normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
+ if (attrs[normalizedName]) {
+ return attrs[normalizedName];
+ }
+ }
+
+ // fallback on unprefixed
+ return attrs[getNormalizedName(attrs, attrName)];
+ }
+
+ function watchResponsiveAttributes(attrNames, attrs, watchFn) {
+ var unwatchFns = [];
+ attrNames.forEach(function(attrName) {
+ var normalizedName = getNormalizedName(attrs, attrName);
+ if (attrs[normalizedName]) {
+ unwatchFns.push(
+ attrs.$observe(normalizedName, angular.bind(void 0, watchFn, null)));
+ }
+
+ for (var mediaName in MEDIA) {
+ normalizedName = getNormalizedName(attrs, attrName + '-' + mediaName);
+ if (!attrs[normalizedName]) {
+ return;
+ }
+
+ unwatchFns.push(attrs.$observe(normalizedName, angular.bind(void 0, watchFn, mediaName)));
+ }
+ });
+
+ return function unwatch() {
+ unwatchFns.forEach(function(fn) { fn(); })
+ };
+ }
+
+ // Improves performance dramatically
+ function getNormalizedName(attrs, attrName) {
+ return normalizeCache[attrName] ||
+ (normalizeCache[attrName] = attrs.$normalize(attrName));
+ }
+}
diff --git a/src/app/common/services/oc-parameters.js b/src/app/common/services/oc-parameters.js
new file mode 100644
index 00000000..17eab7e3
--- /dev/null
+++ b/src/app/common/services/oc-parameters.js
@@ -0,0 +1,49 @@
+angular.module('orderCloud')
+ .factory('ocParameters', OrderCloudParametersService)
+;
+
+function OrderCloudParametersService() {
+ var service = {
+ Get: _get, //get params for use in OrderCloud service
+ Create: _create //create params obj ready for use in OrderCloud $state.go()
+ };
+
+ function _get(stateParams, suffix) {
+ var parameters = angular.copy(stateParams);
+ var suffixParams;
+ parameters.filters = parameters.filters ? JSON.parse(parameters.filters) : null;
+ parameters.from ? parameters.from = new Date(parameters.from) : angular.noop(); //Translate date string to date obj
+ parameters.to ? parameters.to = new Date(parameters.to) : angular.noop(); //Translate date string to date obj
+ if (suffix) {
+ suffixParams = {};
+ angular.forEach(parameters, function(val, key) {
+ suffixParams[key.split(suffix)[0]] = val;
+ });
+ }
+ return suffixParams || parameters;
+ }
+
+ function _create(params, resetPage, suffix) {
+ var parameters = angular.copy(params);
+ var suffixParams;
+ resetPage ? parameters.page = null : angular.noop(); //Reset page when filters are applied
+ if (parameters.filters) {
+ parameters.filters.orderType == '' ? delete parameters.filters.orderType : angular.noop();
+ parameters.filters.type == '' ? delete parameters.filters.type : angular.noop();
+ parameters.filters.status == '' ? delete parameters.filters.status : angular.noop();
+ parameters.filters = JSON.stringify(parameters.filters); //Translate filter object to string
+ parameters.filters == '{}' ? parameters.filters = null : angular.noop(); //Null out the filter string if it's an empty obj
+ }
+ parameters.from ? parameters.from = parameters.from.toISOString() : angular.noop();
+ parameters.to ? parameters.to = parameters.to.toISOString() : angular.noop();
+ if (suffix) {
+ suffixParams = {};
+ angular.forEach(parameters, function(val, key) {
+ suffixParams[key + suffix] = val;
+ });
+ }
+ return suffixParams || parameters;
+ }
+
+ return service;
+}
\ No newline at end of file
diff --git a/src/app/common/templates/card.product.tpl.html b/src/app/common/templates/card.product.tpl.html
new file mode 100644
index 00000000..c32eb9d0
--- /dev/null
+++ b/src/app/common/templates/card.product.tpl.html
@@ -0,0 +1,17 @@
+
+
+
+
+ {{product.ID}}
+ {{product.StandardPriceSchedule.PriceBreaks[0].Price | currency}} x {{product.StandardPriceSchedule.PriceBreaks[0].Quantity}}
+
+
{{product.Name || product.ID}}
+
+
{{product.Description}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/common/templates/confirm.modal.tpl.html b/src/app/common/templates/confirm.modal.tpl.html
new file mode 100644
index 00000000..325c1d7b
--- /dev/null
+++ b/src/app/common/templates/confirm.modal.tpl.html
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/app/common/templates/quantityInput.tpl.html b/src/app/common/templates/quantityInput.tpl.html
new file mode 100644
index 00000000..e6cefd8d
--- /dev/null
+++ b/src/app/common/templates/quantityInput.tpl.html
@@ -0,0 +1,55 @@
+
+
+
+
+
diff --git a/src/app/common/templates/view.loading.tpl.html b/src/app/common/templates/view.loading.tpl.html
new file mode 100644
index 00000000..9203bf46
--- /dev/null
+++ b/src/app/common/templates/view.loading.tpl.html
@@ -0,0 +1,9 @@
+
+
diff --git a/src/app/common/tests/oc-lineitems.spec.js b/src/app/common/tests/oc-lineitems.spec.js
new file mode 100644
index 00000000..e2bebec7
--- /dev/null
+++ b/src/app/common/tests/oc-lineitems.spec.js
@@ -0,0 +1,42 @@
+describe('Factory: ocLineItem', function() {
+ var scope,
+ q,
+ oc,
+ _ocLineItems,
+ order = {
+ ID: "FAKE_ORDER_ID",
+ LineItems: []
+ },
+ product = {
+ ID: "FAKE_PRODUCT_ID",
+ Quantity: 2
+ };
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(inject(function($rootScope, $q, OrderCloud, ocLineItems) {
+ scope = $rootScope.$new();
+ q = $q;
+ oc = OrderCloud;
+ _ocLineItems = ocLineItems;
+ }));
+ describe('AddItem', function() {
+ it ('should start up a new $q.defer()', function() {
+ spyOn(q, 'defer').and.callThrough();
+ _ocLineItems.AddItem(order, product);
+ expect(q.defer).toHaveBeenCalled();
+ });
+ it ('should call OrderCloud.LineItems.Create()', function() {
+ var defer = q.defer();
+ defer.resolve("NEW_LINE_ITEM");
+ spyOn(oc.LineItems, 'Create').and.returnValue(defer.promise);
+
+ _ocLineItems.AddItem(order, product);
+ expect(oc.LineItems.Create).toHaveBeenCalledWith(order.ID, {
+ ProductID: product.ID,
+ Quantity: product.Quantity,
+ Specs: [],
+ ShippingAddressID: null
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/app/favoriteOrders/favoriteOrder.js b/src/app/favoriteOrders/favoriteOrder.js
new file mode 100644
index 00000000..011d526b
--- /dev/null
+++ b/src/app/favoriteOrders/favoriteOrder.js
@@ -0,0 +1,46 @@
+angular.module('orderCloud')
+ .component('ordercloudFavoriteOrder', {
+ bindings:{
+ currentUser: '<',
+ order: '<'
+ },
+ templateUrl: 'favoriteOrders/templates/favoriteOrder.component.tpl.html',
+ controller: FavoriteOrderCtrl
+ });
+
+function FavoriteOrderCtrl(OrderCloud, toastr){
+ var vm = this;
+ vm.$onInit = function(){
+ vm.hasFavorites = !!vm.currentUser && !!vm.currentUser.xp && !!vm.currentUser.xp.FavoriteOrders;
+ vm.isFavorited = !!vm.hasFavorites && vm.currentUser.xp.FavoriteOrders.indexOf(vm.order.ID) > -1;
+ };
+
+ vm.toggleFavoriteOrder = function(){
+ if (vm.hasFavorites && vm.isFavorited){
+ removeOrder();
+ } else if (vm.hasFavorites && !vm.isFavorited) {
+ addOrder(vm.currentUser.xp.FavoriteOrders);
+ } else {
+ addOrder([]);
+ }
+ };
+
+ function addOrder(existingList) {
+ existingList.push(vm.order.ID);
+ OrderCloud.Me.Patch({xp: {FavoriteOrders: existingList}})
+ .then(function(){
+ vm.isFavorited = true;
+ toastr.success('Order added to your favorites', 'Success');
+ });
+ }
+
+ function removeOrder(){
+ var updatedList = _.without(vm.currentUser.xp.FavoriteOrders, vm.order.ID);
+ OrderCloud.Me.Patch({xp: {FavoriteOrders: updatedList}})
+ .then(function(){
+ vm.isFavorited = false;
+ vm.currentUser.xp.FavoriteOrders = updatedList;
+ toastr.success('Order removed from your favorites', 'Success');
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/app/favoriteOrders/templates/favoriteOrder.component.tpl.html b/src/app/favoriteOrders/templates/favoriteOrder.component.tpl.html
new file mode 100644
index 00000000..6659752e
--- /dev/null
+++ b/src/app/favoriteOrders/templates/favoriteOrder.component.tpl.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/favoriteOrders/tests/favoriteOrder.spec.js b/src/app/favoriteOrders/tests/favoriteOrder.spec.js
new file mode 100644
index 00000000..2d7d3ecf
--- /dev/null
+++ b/src/app/favoriteOrders/tests/favoriteOrder.spec.js
@@ -0,0 +1,69 @@
+describe('Component: FavoriteOrders', function(){
+ var q,
+ oc,
+ scope,
+ toaster
+ ;
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(inject(function($q, OrderCloud, $rootScope, toastr){
+ q = $q;
+ oc = OrderCloud;
+ scope = $rootScope.$new();
+ toaster = toastr;
+ }));
+ describe('Controller: FavoriteOrderCtrl', function(){
+ var favoriteOrderCtrl;
+ beforeEach(inject(function($componentController){
+ favoriteOrderCtrl = $componentController('ordercloudFavoriteOrder', {
+ $scope: scope,
+ OrderCloud: oc,
+ toastr: toaster
+ });
+
+ }));
+ describe('toggleFavoriteOrder', function(){
+ var mockOrderID,
+ mockFavoriteOrder
+ ;
+ beforeEach(function(){
+ mockOrderID = 'OrderID123';
+ favoriteOrderCtrl.order = {ID: mockOrderID};
+ mockFavoriteOrder = 'FavoriteOrder1';
+ favoriteOrderCtrl.currentUser = {xp: {FavoriteOrders: [mockFavoriteOrder]}};
+
+ var defer = q.defer();
+ defer.resolve();
+ spyOn(oc.Me, 'Patch').and.returnValue(defer.promise);
+ spyOn(toaster, 'success');
+ });
+ it('should add to favorites if user doesnt have any favorite orders', function(){
+ favoriteOrderCtrl.hasFavorites = false;
+ favoriteOrderCtrl.toggleFavoriteOrder();
+ expect(oc.Me.Patch).toHaveBeenCalledWith({xp: {FavoriteOrders: [mockOrderID]}});
+ scope.$digest();
+ expect(favoriteOrderCtrl.isFavorited).toBe(true);
+ expect(toaster.success).toHaveBeenCalledWith('Order added to your favorites', 'Success');
+ });
+ it('should add to favorites if user has favorites list, but the order isnt included in the list', function(){
+ favoriteOrderCtrl.hasFavorites = true;
+ favoriteOrderCtrl.isFavorited = false;
+ favoriteOrderCtrl.toggleFavoriteOrder();
+ expect(oc.Me.Patch).toHaveBeenCalledWith({xp: {FavoriteOrders: [mockFavoriteOrder, mockOrderID]}});
+ scope.$digest();
+ expect(favoriteOrderCtrl.isFavorited).toBe(true);
+ expect(toaster.success).toHaveBeenCalledWith('Order added to your favorites', 'Success');
+ });
+ it('should remove order from favorite list, if order is already on list', function(){
+ favoriteOrderCtrl.hasFavorites = true;
+ favoriteOrderCtrl.isFavorited = true;
+ favoriteOrderCtrl.order = {ID: mockFavoriteOrder};
+ favoriteOrderCtrl.toggleFavoriteOrder();
+ expect(oc.Me.Patch).toHaveBeenCalledWith({xp: {FavoriteOrders: [ ]}});
+ scope.$digest();
+ expect(favoriteOrderCtrl.isFavorited).toBe(false);
+ expect(toaster.success).toHaveBeenCalledWith('Order removed from your favorites', 'Success');
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/app/favoriteProducts/favoriteProducts.js b/src/app/favoriteProducts/favoriteProducts.js
new file mode 100644
index 00000000..69cb7225
--- /dev/null
+++ b/src/app/favoriteProducts/favoriteProducts.js
@@ -0,0 +1,145 @@
+angular.module('orderCloud')
+ .config(FavoriteProductsConfig)
+ .directive('ordercloudFavoriteProduct', FavoriteProductDirective)
+ .controller('FavoriteProductsCtrl', FavoriteProductsController)
+ .controller('FavoriteProductCtrl', FavoriteProductController)
+;
+
+function FavoriteProductsConfig($stateProvider){
+ $stateProvider
+ .state('favoriteProducts', {
+ parent: 'account',
+ templateUrl: 'favoriteProducts/templates/favoriteProducts.tpl.html',
+ url: '/favorite-products?search?page?pageSize?searchOn?sortBy?filters?depth',
+ controller: 'FavoriteProductsCtrl',
+ controllerAs: 'favoriteProducts',
+ data: {
+ pageTitle: "Favorite Products"
+ },
+ resolve: {
+ Parameters: function ($stateParams, ocParameters) {
+ return ocParameters.Get($stateParams);
+ },
+ FavoriteProducts: function(OrderCloud, Parameters, CurrentUser){
+ if (CurrentUser.xp && CurrentUser.xp.FavoriteProducts.length) {
+ return OrderCloud.Me.ListProducts(Parameters.search, Parameters.page, Parameters.pageSize || 6, Parameters.searchOn, Parameters.sortBy, {ID: CurrentUser.xp.FavoriteProducts.join('|')});
+ } else {
+ return null;
+ }
+ }
+ }
+ });
+}
+
+function FavoriteProductsController(ocParameters, OrderCloud, $state, $ocMedia, Parameters, CurrentUser, FavoriteProducts){
+ var vm = this;
+ vm.currentUser = CurrentUser;
+ vm.list = FavoriteProducts;
+ vm.parameters = Parameters;
+
+ vm.sortSelection = Parameters.sortBy ? (Parameters.sortBy.indexOf('!') == 0 ? Parameters.sortBy.split('!')[1] : Parameters.sortBy) : null;
+
+ //Filtering and Search Functionality
+ //check if filters are applied
+ vm.filtersApplied = vm.parameters.filters || ($ocMedia('max-width: 767px') && vm.sortSelection);
+ vm.showFilters = vm.filtersApplied;
+
+
+ //reload the state with new filters
+ vm.filter = function(resetPage) {
+ $state.go('.', ocParameters.Create(vm.parameters, resetPage));
+ };
+
+ //clear the relevant filters, reload the state & reset the page
+ vm.clearFilters = function() {
+ vm.parameters.filters = null;
+ $ocMedia('max-width: 767px') ? vm.parameters.sortBy = null : angular.noop();
+ vm.filter(true);
+ };
+
+ vm.updateSort = function(value) {
+ value ? angular.noop() : value = vm.sortSelection;
+ switch (vm.parameters.sortBy) {
+ case value:
+ vm.parameters.sortBy = '!' + value;
+ break;
+ case '!' + value:
+ vm.parameters.sortBy = null;
+ break;
+ default:
+ vm.parameters.sortBy = value;
+ }
+ vm.filter(false);
+ };
+
+ vm.reverseSort = function() {
+ Parameters.sortBy.indexOf('!') == 0 ? vm.parameters.sortBy = Parameters.sortBy.split('!')[1] : vm.parameters.sortBy = '!' + Parameters.sortBy;
+ vm.filter(false);
+ };
+
+ //reload the state with the incremented page parameter
+ vm.pageChanged = function() {
+ $state.go('.', {
+ page: vm.list.Meta.Page
+ });
+ };
+
+ //load the next page of results with all the same parameters
+ vm.loadMore = function() {
+ return OrderCloud.Me.ListProducts(Parameters.search, vm.list.Meta.Page + 1, Parameters.pageSize || vm.list.Meta.PageSize, Parameters.searchOn, Parameters.sortBy, Parameters.filters)
+ .then(function(data) {
+ vm.list.Items = vm.list.Items.concat(data.Items);
+ vm.list.Meta = data.Meta;
+ });
+ };
+}
+
+function FavoriteProductDirective(){
+ return {
+ scope: {
+ currentUser: '=',
+ product: '='
+ },
+ restrict: 'E',
+ templateUrl: 'favoriteProducts/templates/ordercloud-favorite-product.tpl.html',
+ controller: 'FavoriteProductCtrl',
+ controllerAs: 'favoriteProduct'
+ };
+}
+
+function FavoriteProductController($scope, OrderCloud, toastr){
+ var vm = this;
+ vm.hasFavorites = $scope.currentUser && $scope.currentUser.xp && $scope.currentUser.xp.FavoriteProducts;
+ vm.isFavorited = vm.hasFavorites && $scope.currentUser.xp.FavoriteProducts.indexOf($scope.product.ID) > -1;
+
+ vm.toggleFavoriteProduct = function(){
+ if (vm.hasFavorites){
+ if (vm.isFavorited){
+ removeProduct();
+ } else {
+ addProduct($scope.currentUser.xp.FavoriteProducts);
+ }
+
+ } else {
+ addProduct([]);
+ }
+ function addProduct(existingList){
+ existingList.push($scope.product.ID);
+ OrderCloud.Me.Patch({xp: {FavoriteProducts: existingList}})
+ .then(function(){
+ vm.isFavorited = true;
+ toastr.success($scope.product.Name + ' was added to your favorites');
+ });
+ }
+ function removeProduct(){
+ var updatedList = _.without($scope.currentUser.xp.FavoriteProducts, $scope.product.ID);
+ OrderCloud.Me.Patch({xp: {FavoriteProducts: updatedList}})
+ .then(function(){
+ vm.isFavorited = false;
+ $scope.currentUser.xp.FavoriteProducts = updatedList;
+ toastr.success($scope.product.Name + ' was removed from your favorites');
+ });
+ }
+ };
+}
+
diff --git a/src/app/favoriteProducts/templates/favoriteProducts.tpl.html b/src/app/favoriteProducts/templates/favoriteProducts.tpl.html
new file mode 100644
index 00000000..e3f51175
--- /dev/null
+++ b/src/app/favoriteProducts/templates/favoriteProducts.tpl.html
@@ -0,0 +1,56 @@
+
+
+
+ No matches found.
+
+
+
+
+
+ Load More
+
+
\ No newline at end of file
diff --git a/src/app/favoriteProducts/templates/ordercloud-favorite-product.tpl.html b/src/app/favoriteProducts/templates/ordercloud-favorite-product.tpl.html
new file mode 100644
index 00000000..74548b95
--- /dev/null
+++ b/src/app/favoriteProducts/templates/ordercloud-favorite-product.tpl.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/favoriteProducts/tests/favoriteProducts.spec.js b/src/app/favoriteProducts/tests/favoriteProducts.spec.js
new file mode 100644
index 00000000..ad06db3b
--- /dev/null
+++ b/src/app/favoriteProducts/tests/favoriteProducts.spec.js
@@ -0,0 +1,92 @@
+describe('Component: FavoriteProducts', function(){
+ var q,
+ oc,
+ scope,
+ ocParams,
+ parameters,
+ favoriteProducts,
+ currentUser,
+ toaster,
+ product;
+
+ beforeEach(module('orderCloud'));
+ beforeEach(module('orderCloud.sdk'));
+ beforeEach(module(function($provide) {
+ $provide.value('Parameters', {search:null, page: null, pageSize: null, searchOn: null, sortBy: null, userID: null, userGroupID: null, level: null, buyerID: null});
+ $provide.value('FavoriteProducts', []);
+ $provide.value('CurrentUser', {xp: {FavoriteProducts: ['favoriteProduct']}});
+ }));
+ beforeEach(inject(function($q, OrderCloud, $rootScope, Parameters, ocParameters, CurrentUser, FavoriteProducts, toastr){
+ q = $q;
+ oc = OrderCloud;
+ scope = $rootScope.$new();
+ parameters = Parameters;
+ ocParams = ocParameters;
+ favoriteProducts = FavoriteProducts;
+ currentUser = CurrentUser;
+ toaster = toastr;
+ product = {
+ ID: 'productID'
+ };
+ }));
+
+ describe('State: favoriteProducts', function(){
+ var state;
+ beforeEach(inject(function($state){
+ state = $state.get('favoriteProducts');
+ var defer = q.defer();
+ defer.resolve();
+ spyOn(ocParams, 'Get').and.returnValue(null);
+ spyOn(oc.Me, 'ListProducts').and.returnValue(defer.promise);
+ }));
+ it('should resolve Parameters', inject(function($injector){
+ $injector.invoke(state.resolve.Parameters);
+ expect(ocParams.Get).toHaveBeenCalled();
+ }));
+ it('should resolve FavoriteProducts', inject(function(CurrentUser, $injector){
+ $injector.invoke(state.resolve.FavoriteProducts);
+ currentUser.xp = {favoriteProducts: 'favoriteProduct'};
+ expect(oc.Me.ListProducts).toHaveBeenCalledWith(parameters.search, parameters.page, parameters.pageSize || 6, parameters.searchOn, parameters.sortBy, {ID: currentUser.xp.favoriteProducts});
+ }));
+ });
+ describe('Controller: FavoriteProductCtrl', function(){
+ var favoriteProductCtrl;
+ beforeEach(inject(function($state, $controller, CurrentUser){
+ scope ={};
+ scope.currentUser = CurrentUser;
+ scope.product = {
+ ID: 'productID'
+ };
+ favoriteProductCtrl = $controller('FavoriteProductCtrl', {
+ $scope: scope,
+ OrderCloud: oc,
+ toastr: toaster
+ });
+
+ }));
+ describe('toggleFavoriteProduct', function(){
+ beforeEach(function(){
+ var defer = q.defer();
+ defer.resolve();
+ spyOn(oc.Me, 'Patch').and.returnValue(defer.promise);
+ spyOn(_, 'without').and.returnValue('updatedList');
+ spyOn(toaster, 'success');
+ });
+ it('should call the Me Patch method when deleting a product', function(){
+ var updatedList = 'updatedList';
+ favoriteProductCtrl.hasFavorites = true;
+ favoriteProductCtrl.isFavorited = true;
+ favoriteProductCtrl.toggleFavoriteProduct();
+ expect(_.without).toHaveBeenCalled();
+ expect(oc.Me.Patch).toHaveBeenCalledWith({xp: {FavoriteProducts: updatedList}});
+ });
+ it('should call the Me Patch method when removing a product', function(){
+ var existingList = ['favoriteProduct', 'productID'];
+ favoriteProductCtrl.hasFavorites = true;
+ favoriteProductCtrl.isFavorited = false;
+ favoriteProductCtrl.toggleFavoriteProduct();
+ expect(oc.Me.Patch).toHaveBeenCalledWith({xp: {FavoriteProducts: existingList}});
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/app/home/home.js b/src/app/home/home.js
index 157f600d..c5680f00 100644
--- a/src/app/home/home.js
+++ b/src/app/home/home.js
@@ -10,7 +10,14 @@ function HomeConfig($stateProvider) {
url: '/home',
templateUrl: 'home/templates/home.tpl.html',
controller: 'HomeCtrl',
+<<<<<<< HEAD
controllerAs: 'home'
+=======
+ controllerAs: 'home',
+ data: {
+ pageTitle: 'Home'
+ }
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
})
;
}
diff --git a/src/app/home/templates/home.tpl.html b/src/app/home/templates/home.tpl.html
index 989a1292..49fa3fe8 100644
--- a/src/app/home/templates/home.tpl.html
+++ b/src/app/home/templates/home.tpl.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
+=======
+
+
+
+ Welcome to the OrderCloud Marketplace App!
+ Learn more »
+
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
\ No newline at end of file
diff --git a/src/app/login/login.js b/src/app/login/login.js
index 26c7d77e..948ace7a 100644
--- a/src/app/login/login.js
+++ b/src/app/login/login.js
@@ -2,6 +2,16 @@ angular.module('orderCloud')
.config(LoginConfig)
.factory('LoginService', LoginService)
.controller('LoginCtrl', LoginController)
+<<<<<<< HEAD
+=======
+ .directive('prettySubmit', function () {
+ return function (scope, element) {
+ $(element).submit(function(event) {
+ event.preventDefault();
+ });
+ };
+ })
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
;
function LoginConfig($stateProvider) {
@@ -15,11 +25,19 @@ function LoginConfig($stateProvider) {
;
}
+<<<<<<< HEAD
function LoginService($q, $window, $state, toastr, OrderCloud, TokenRefresh, clientid, buyerid, anonymous) {
+=======
+function LoginService($q, $window, $state, $cookies, toastr, OrderCloud, clientid, buyerid, anonymous) {
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
return {
SendVerificationCode: _sendVerificationCode,
ResetPassword: _resetPassword,
RememberMe: _rememberMe,
+<<<<<<< HEAD
+=======
+ AuthAnonymous: _authAnonymous,
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
Logout: _logout
};
@@ -63,15 +81,32 @@ function LoginService($q, $window, $state, toastr, OrderCloud, TokenRefresh, cli
return deferred.promise;
}
+<<<<<<< HEAD
function _logout(){
OrderCloud.Auth.RemoveToken();
OrderCloud.Auth.RemoveImpersonationToken();
OrderCloud.BuyerID.Set(null);
TokenRefresh.RemoveToken();
+=======
+ function _authAnonymous() {
+ return OrderCloud.Auth.GetToken('')
+ .then(function(data) {
+ OrderCloud.BuyerID.Set(buyerid);
+ OrderCloud.Auth.SetToken(data.access_token);
+ $state.go('home');
+ });
+ }
+
+ function _logout() {
+ angular.forEach($cookies.getAll(), function(val, key) {
+ $cookies.remove(key);
+ });
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
$state.go(anonymous ? 'home' : 'login', {}, {reload: true});
}
function _rememberMe() {
+<<<<<<< HEAD
TokenRefresh.GetToken()
.then(function (refreshToken) {
if (refreshToken) {
@@ -92,6 +127,28 @@ function LoginService($q, $window, $state, toastr, OrderCloud, TokenRefresh, cli
}
function LoginController($state, $stateParams, $exceptionHandler, OrderCloud, LoginService, TokenRefresh, buyerid) {
+=======
+ var availableRefreshToken = OrderCloud.Refresh.ReadToken() || null;
+
+ if (availableRefreshToken) {
+ OrderCloud.Refresh.GetToken(availableRefreshToken)
+ .then(function(data) {
+ OrderCloud.BuyerID.Set(buyerid);
+ OrderCloud.Auth.SetToken(data.access_token);
+ $state.go('home');
+ })
+ .catch(function () {
+ toastr.error('Your token has expired, please log in again.');
+ _logout();
+ });
+ } else {
+ _logout();
+ }
+ }
+}
+
+function LoginController($state, $stateParams, $exceptionHandler, OrderCloud, LoginService, buyerid) {
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
var vm = this;
vm.credentials = {
Username: null,
@@ -105,9 +162,19 @@ function LoginController($state, $stateParams, $exceptionHandler, OrderCloud, Lo
vm.rememberStatus = false;
vm.submit = function() {
+<<<<<<< HEAD
OrderCloud.Auth.GetToken(vm.credentials)
.then(function(data) {
vm.rememberStatus ? TokenRefresh.SetToken(data['refresh_token']) : angular.noop();
+=======
+ $('#Username').blur();
+ $('#Password').blur();
+ $('#Remember').blur();
+ $('#submit_login').blur();
+ vm.loading = OrderCloud.Auth.GetToken(vm.credentials)
+ .then(function(data) {
+ vm.rememberStatus ? OrderCloud.Refresh.SetToken(data['refresh_token']) : angular.noop();
+>>>>>>> 281bb9e29d0e44c929457c755c5b59714e368ee2
OrderCloud.BuyerID.Set(buyerid);
OrderCloud.Auth.SetToken(data['access_token']);
$state.go('home');
diff --git a/src/app/login/templates/login.tpl.html b/src/app/login/templates/login.tpl.html
index 48d986c8..b1d0aa64 100644
--- a/src/app/login/templates/login.tpl.html
+++ b/src/app/login/templates/login.tpl.html
@@ -1,3 +1,4 @@
+<<<<<<< HEAD