1 /*jshint indent:2, curly:true, eqeqeq:true, immed:true, latedef:true,
2 newcap:true, noarg:true, regexp:true, undef:true, strict:true, trailing:true,
4 /*global XV:true, XT:true, _:true, console:true, XM:true, Backbone:true,
5 require:true, assert:true, setTimeout:true, clearTimeout:true, exports:true,
6 it:true, describe:true, beforeEach:true, before:true, enyo:true */
11 var async = require("async"),
12 _ = require("underscore"),
13 smoke = require("../lib/smoke"),
14 common = require("../lib/common"),
15 assert = require("chai").assert,
29 Returns (Credit memo) are used to issue credit for sold Inventory
32 @property {String} number that is the documentKey and idAttribute
33 @property {Date} returnDate required default today
34 @property {Boolean} isPosted required, defaulting to false, read only
35 @property {Boolean} isVoid required, defaulting to false, read only
36 @property {BillingCustomer} customer required
37 @property {String} billtoName
38 @property {String} billtoAddress1
39 @property {String} billtoAddress2
40 @property {String} billtoAddress3
41 @property {String} billtoCity
42 @property {String} billtoState
43 @property {String} billtoPostalCode
44 @property {String} billtoCountry
45 @property {Currency} currency
46 @property {SalesRep} salesRep
47 @property {Percent} commission required, default 0
48 @property {SaleType} saleType
49 @property {String} customerPurchaseOrderNumber
50 @property {TaxZone} taxZone
51 @property {String} notes
52 @property {Money} subtotal the sum of the extended price of all line items
53 @property {Money} taxTotal the sum of all taxes inluding line items, freight and
55 @property {Money} miscCharge read only (will be re-implemented as editable by Ledger)
56 @property {Money} total the calculated total of subtotal + freight + tax + miscCharge
57 @property {Money} balance the sum of total - allocatedCredit - authorizedCredit -
59 - If sum calculates to less than zero, then the balance is zero.
60 @property {ReturnAllocation} allocations
61 @property {ReturnTax} taxAdjustments
62 @property {ReturnLine} lineItems
65 recordType: "XM.Return",
66 collectionType: "XM.ReturnListItemCollection",
70 @description The Return collection is not cached.
73 listKind: "XV.ReturnList",
74 instanceOf: "XM.Document",
78 @description Return is lockable.
84 @description The ID attribute is "number", which will be automatically uppercased.
86 idAttribute: "number",
87 enforceUpperKey: true,
88 attributes: ["number", "returnDate", "isPosted", "isVoid", "customer",
89 "billtoName", "billtoAddress1", "billtoAddress2", "billtoAddress3",
90 "billtoCity", "billtoState", "billtoPostalCode", "billtoCountry",
91 "currency", "salesRep", "commission",
92 "saleType", "customerPurchaseOrderNumber", "taxZone", "notes",
93 "subtotal", "taxTotal", "miscCharge", "total", "balance", "allocations",
94 "taxAdjustments", "lineItems"],
95 requiredAttributes: ["number", "returnDate", "isPosted", "isVoid",
96 "customer", "commission"],
98 returnDate: new Date(),
106 @description Used in the billing module
108 extensions: ["billing"],
112 @description Users can create, update, and delete Returns if they have the
113 MaintainCreditMemos privilege.
118 @description Users can read Returns if they have
119 the ViewCreditMemos privilege.
122 createUpdateDelete: "MaintainCreditMemos", //Privileges from the desktop client
123 read: "ViewCreditMemos"
126 //customer: {number: "XRETAIL"}
127 customer: {number: "TTOYS"}
129 updatableField: "notes",
130 beforeSaveActions: [{it: 'sets up a valid line item',
131 action: require("./sales_order").getBeforeSaveAction("XM.ReturnLine")}],
132 beforeSaveUIActions: [{it: 'sets up a valid line item',
133 action: function (workspace, done) {
135 // XXX we really need a standard way of doing this
136 primeSubmodels = require("./sales_order").primeSubmodels;
138 primeSubmodels(function (submodels) {
139 workspace.$.returnLineItemBox.newItem();
140 gridRow = workspace.$.returnLineItemBox.$.editableGridRow;
141 gridRow.$.itemSiteWidget.doValueChange({value: {item: submodels.itemModel,
142 site: submodels.siteModel}});
143 gridRow.$.quantityWidget.doValueChange({value: 5});
144 gridRow.$.creditedWidget.doValueChange({value: 5});
145 setTimeout(function () {
154 var additionalTests = function () {
155 describe("ReturnLine", function () {
156 before(function (done) {
159 common.fetchModel(bpaint, XM.ItemRelation, {number: "BPAINT1"}, function (err, model) {
165 common.fetchModel(btruck, XM.ItemRelation, {number: "BTRUCK1"}, function (err, model) {
171 common.initializeModel(returnModel, XM.Return, function (err, model) {
177 common.initializeModel(lineModel, XM.ReturnLine, function (err, model) {
183 usd = _.find(XM.currencies.models, function (model) {
184 return model.get("abbreviation") === "USD";
186 gbp = _.find(XM.currencies.models, function (model) {
187 return model.get("abbreviation") === "GBP";
194 @member ReturnLineTax
196 @description Contains the tax of an Return line.
197 @property {String} uuid The ID attribute
198 @property {TaxType} taxType
199 @property {TaxCode} taxCode
200 @property {Money} amount
202 it("has ReturnLineTax as a nested-only model extending XM.Model", function () {
203 var attrs = ["uuid", "taxType", "taxCode", "amount"],
206 assert.isFunction(XM.ReturnLineTax);
207 model = new XM.ReturnLineTax();
208 assert.isTrue(model instanceof XM.Model);
209 assert.equal(model.idAttribute, "uuid");
210 assert.equal(_.difference(attrs, model.getAttributeNames()).length, 0);
212 it.skip("XM.ReturnLineTax can be created, updated and deleted", function () {
213 // TODO: put under test (code is written)
215 it.skip("A view should be used underlying XM.ReturnLineTax that does nothing " +
216 "after insert, update or delete (existing table triggers for line items will " +
217 "take care of populating this data correctly)", function () {
218 // TODO: put under test (code is written)
223 @description Represents a line of an Return. Only ever used within the context of an
225 @property {String} uuid The ID attribute
226 @property {Number} lineNumber required
227 @property {ItemRelation} item
228 @property {SiteRelation} site defaults to the system default site
229 @property {ReasonCode} ReasonCode
230 @property {Quantity} quantity
231 @property {Unit} quantityUnit
232 @property {Number} quantityUnitRatio
233 @property {Quantity} credited
234 @property {Number} discountPercentFromSale
235 @property {Number} customerPrice
236 @property {SalesPrice} price
237 @property {Unit} priceUnit
238 @property {Number} priceUnitRatio
239 @property {ExtendedPrice} "extendedPrice" = credited * quantityUnitRatio *
240 (price / priceUnitRatio)
241 @property {Number} notes
242 @property {TaxType} taxType
243 @property {Money} taxTotal sum of all taxes
244 @property {ReturnLineTax} taxes
245 @property {SalesOrderLine} salesOrderLine Added by sales extension
247 it("A nested only model called XM.ReturnLine extending " +
248 "XM.Model should exist", function () {
250 assert.isFunction(XM.ReturnLine);
251 lineModel = new XM.ReturnLine();
252 assert.isTrue(lineModel instanceof XM.Model);
253 assert.equal(lineModel.idAttribute, "uuid");
255 it.skip("ReturnLine should include attributes x, y, and z", function () {
256 // TODO: put under test (code is written)
261 @description ReturnLine keeps track of the available selling units of measure
262 based on the selected item, in the "sellingUnits" property
264 it("XM.ReturnLine should include a property \"sellingUnits\" that is an array " +
265 "of available selling units of measure based on the selected item", function () {
266 var lineModel = new XM.ReturnLine();
267 assert.isObject(lineModel.sellingUnits);
272 @description When the item is changed the following should be updated from item information:
273 sellingUnits, quantityUnit, quantityUnitRatio, priceUnit, priceUnitRatio, unitCost
274 and taxType. Then, the price should be recalculated.
276 it("XM.ReturnLine should have a fetchSellingUnits function that updates " +
277 "sellingUnits based on the item selected", function () {
278 assert.isFunction(lineModel.fetchSellingUnits);
280 it("itemDidChange should recalculate sellingUnits, quantityUnit, quantityUnitRatio, " +
281 "priceUnit, priceUnitRatio, " +
282 "and taxType. Also calculatePrice should be executed.", function (done) {
285 assert.equal(lineModel.sellingUnits.length, 0);
286 assert.isNull(lineModel.get("quantityUnit"));
287 assert.isNull(lineModel.get("priceUnit"));
288 assert.isNull(lineModel.get("taxType"));
289 lineModel.set({item: btruck});
291 setTimeout(function () {
292 assert.equal(lineModel.sellingUnits.length, 1);
293 assert.equal(lineModel.sellingUnits.models[0].id, "EA");
294 assert.equal(lineModel.get("quantityUnit").id, "EA");
295 assert.equal(lineModel.get("priceUnit").id, "EA");
296 assert.equal(lineModel.get("priceUnitRatio"), 1);
297 assert.equal(lineModel.get("quantityUnitRatio"), 1);
298 assert.equal(lineModel.get("taxType").id, "Taxable");
300 }, 3000); // TODO: use an event. headache because we have to wait for several
305 @description quantity and credited values can be fractional only if the item allows it
307 it("When the item isFractional attribute === false, decimal numbers should not be allowed " +
308 "for quantity and credited values.", function () {
309 lineModel.set({quantity: 1, credited: 1.5});
310 assert.isObject(lineModel.validate(lineModel.attributes));
311 lineModel.set({credited: 2});
312 assert.isUndefined(JSON.stringify(lineModel.validate(lineModel.attributes)));
313 lineModel.set({quantity: 1.5});
314 assert.isObject(lineModel.validate(lineModel.attributes));
315 lineModel.set({quantity: 2});
316 assert.isUndefined(JSON.stringify(lineModel.validate(lineModel.attributes)));
318 it("When the item isFractional attribute === true, decimal numbers should be allowed " +
319 "for quantity values.", function (done) {
320 lineModel.set({item: bpaint, quantity: 1.5});
321 setTimeout(function () {
322 assert.isUndefined(JSON.stringify(lineModel.validate(lineModel.attributes)));
324 }, 1900); // wait for line._isItemFractional to get updated from the item
326 it("When the item isFractional attribute === true, decimal numbers should be allowed " +
327 "for credited values.", function () {
328 lineModel.set({quantity: 1.5, credited: 2});
329 assert.isUndefined(JSON.stringify(lineModel.validate(lineModel.attributes)));
334 @description Returned and credited should only allow positive values.
336 it("Returned should only allow positive values", function () {
337 lineModel.set({quantity: -1});
338 assert.isObject(lineModel.validate(lineModel.attributes));
339 lineModel.set({quantity: 0});
340 assert.isObject(lineModel.validate(lineModel.attributes));
341 lineModel.set({quantity: 2});
342 assert.isUndefined(JSON.stringify(lineModel.validate(lineModel.attributes)));
344 it("credited should only allow positive values", function () {
345 lineModel.set({credited: -1});
346 assert.isObject(lineModel.validate(lineModel.attributes));
347 lineModel.set({credited: 0});
348 assert.isObject(lineModel.validate(lineModel.attributes));
349 lineModel.set({credited: 2});
350 assert.isUndefined(JSON.stringify(lineModel.validate(lineModel.attributes)));
355 @description When item is unset, all item-related values should be cleared.
357 it("If item is unset, the above values should be cleared.", function (done) {
358 // relies on the fact that the item was set above to something
360 lineModel.set({item: null});
362 setTimeout(function () {
363 assert.equal(lineModel.sellingUnits.length, 0);
364 assert.isNull(lineModel.get("quantityUnit"));
365 assert.isNull(lineModel.get("priceUnit"));
366 assert.isNull(lineModel.get("taxType"));
368 }, 3000); // TODO: use an event. headache because we have to wait for several
373 @description User requires the "OverrideTax" privilege to edit the tax type.
375 it.skip("User requires the OverrideTax privilege to edit the tax type", function () {
376 // TODO: write code and put under test
377 //HINT: Default tax type must be enforced by a trigger on the database if no privilege.
380 it("lineNumber must auto-number itself sequentially", function () {
381 var dummyModel = new XM.ReturnLine();
382 assert.isUndefined(lineModel.get("lineNumber"));
383 returnModel.get("lineItems").add(dummyModel);
384 returnModel.get("lineItems").add(lineModel);
385 assert.equal(lineModel.get("lineNumber"), 2);
386 returnModel.get("lineItems").remove(dummyModel);
387 // TODO: be more thorough
392 @description Currency field should be read only after a line item is added to the Return
394 it("Currency field should be read-only after a line item is added to the Return",
396 assert.isTrue(returnModel.isReadOnly("currency"));
398 it.skip("XM.ReturnLine should have a calculatePrice function that retrieves a price from " +
399 "the customer.itemPrice dispatch function based on the 'credited' value.", function () {
400 // TODO: put under test (code is written)
404 describe("XM.ReturnListItem", function () {
405 // TODO:posting and voiding work, anecdotally. Put it under test.
406 it.skip("XM.ReturnListItem includes a post function that dispatches a " +
407 "XM.Return.post function to the server", function () {
408 var model = new XM.ReturnListItem();
409 assert.isFunction(model.doPost);
411 model.set({number: "999"});
413 success: function () {
414 console.log("success", arguments);
418 console.log("error", arguments);
423 // this should really be under better test
424 it.skip("XM.ReturnListItem includes a void function that dispatches a " +
425 "XM.Return.void function to the server", function () {
426 var model = new XM.ReturnListItem();
427 assert.isFunction(model.doVoid);
430 describe("XM.Return", function () {
431 before(function (done) {
434 common.fetchModel(ttoys, XM.BillingCustomer, {number: "TTOYS"}, function (err, model) {
440 common.fetchModel(vcol, XM.BillingCustomer, {number: "VCOL"}, function (err, model) {
446 common.fetchModel(nctax, XM.TaxZone, {code: "NC TAX"}, function (err, model) {
452 common.fetchModel(nctaxCode, XM.TaxCode, {code: "NC TAX-A"}, function (err, model) {
458 common.initializeModel(ReturnTaxModel, XM.ReturnTax, function (err, model) {
459 ReturnTaxModel = model;
464 common.initializeModel(allocationModel, XM.ReturnAllocation, function (err, model) {
465 allocationModel = model;
473 // Note: the other required fields in taxhist should be populated with the following:
477 // docdate: Return date
478 // taxtype: 3. Yes, 3.
483 @description Return tax adjustments
484 @property {String} uuid
485 @property {TaxCode} taxCode
486 @property {Money} amount
488 it("A nested only model called XM.ReturnTax extending XM.Model should exist", function () {
489 assert.isFunction(XM.ReturnTax);
490 var ReturnTaxModel = new XM.ReturnTax(),
491 attrs = ["uuid", "taxCode", "amount"];
493 assert.isTrue(ReturnTaxModel instanceof XM.Model);
494 assert.equal(ReturnTaxModel.idAttribute, "uuid");
495 assert.equal(_.difference(attrs, ReturnTaxModel.getAttributeNames()).length, 0);
500 @description The Return numbering policy can be determined by the user.
502 it("XM.Return should check the setting for CMNumberGeneration to determine " + //Please change the variable InvcNumberGeneration accordingly
503 "numbering policy", function () {
505 XT.session.settings.set({CMNumberGeneration: "M"});
506 model = new XM.Return();
507 assert.equal(model.numberPolicy, "M");
508 XT.session.settings.set({CMNumberGeneration: "A"});
509 model = new XM.Return();
510 assert.equal(model.numberPolicy, "A");
513 @member ReturnAllocation
515 @description Return-level allocation information
516 @property {String} uuid
517 @property {String} return // XXX String or Number?
518 @property {Money} amount
519 @property {Currency} currency
521 it("A nested only model called XM.ReturnAllocation extending XM.Model " +
522 "should exist", function () {
523 assert.isFunction(XM.ReturnAllocation);
524 var ReturnAllocationModel = new XM.ReturnAllocation(),
525 attrs = ["uuid", "return", "amount", "currency"];
527 assert.isTrue(ReturnAllocationModel instanceof XM.Model);
528 assert.equal(ReturnAllocationModel.idAttribute, "uuid");
529 assert.equal(_.difference(attrs, ReturnAllocationModel.getAttributeNames()).length, 0);
531 it("XM.ReturnAllocation should only be updateable by users with the ApplyARMemos " +
532 "privilege.", function () {
533 XT.session.privileges.attributes.ApplyARMemos = false;
534 assert.isFalse(XM.ReturnAllocation.canCreate());
535 assert.isTrue(XM.ReturnAllocation.canRead());
536 assert.isFalse(XM.ReturnAllocation.canUpdate());
537 assert.isFalse(XM.ReturnAllocation.canDelete());
538 XT.session.privileges.attributes.ApplyARMemos = true;
539 assert.isTrue(XM.ReturnAllocation.canCreate());
540 assert.isTrue(XM.ReturnAllocation.canRead());
541 assert.isTrue(XM.ReturnAllocation.canUpdate());
542 assert.isTrue(XM.ReturnAllocation.canDelete());
545 @member ReturnListItem
547 @description List-view summary information for an Return
548 @property {String} number
549 @property {Boolean} isPrinted
550 @property {BillingCustomer} customer
551 @property {Date} returnDate
552 @property {Money} total
553 @property {Boolean} isPosted
554 @property {Boolean} isOpen
555 @property {Boolean} isVoid
556 @property {String} orderNumber Added by sales extension
558 it("A model called XM.ReturnListItem extending XM.Info should exist", function () {
559 assert.isFunction(XM.ReturnListItem);
560 var ReturnListItemModel = new XM.ReturnListItem(),
561 attrs = ["number", "customer", "returnDate", "total", "isPosted", "isVoid"];
563 assert.isTrue(ReturnListItemModel instanceof XM.Info);
564 assert.equal(ReturnListItemModel.idAttribute, "number");
565 assert.equal(_.difference(attrs, ReturnListItemModel.getAttributeNames()).length, 0);
567 it("Only users that have ViewCreditMemos or MaintainCreditMemos may read " +
568 "XV.ReturnListItem", function () {
569 XT.session.privileges.attributes.ViewCreditMemos = false;
570 XT.session.privileges.attributes.MaintainCreditMemos = false;
571 assert.isFalse(XM.ReturnListItem.canRead());
573 XT.session.privileges.attributes.ViewCreditMemos = true;
574 XT.session.privileges.attributes.MaintainCreditMemos = false;
575 assert.isTrue(XM.ReturnListItem.canRead());
577 XT.session.privileges.attributes.ViewCreditMemos = false;
578 XT.session.privileges.attributes.MaintainCreditMemos = true;
579 assert.isTrue(XM.ReturnListItem.canRead());
581 it("XM.ReturnListItem is not editable", function () {
582 assert.isFalse(XM.ReturnListItem.canCreate());
583 assert.isFalse(XM.ReturnListItem.canUpdate());
584 assert.isFalse(XM.ReturnListItem.canDelete());
586 it.skip("XM.ReturnListItem includes a \"post\" function that dispatches a" +
587 "XM.Return.post function to the server", function () {
589 it.skip("XM.ReturnListItem includes a \"void\" function that dispatches a" +
590 "XM.Return.void function to the server", function () {
594 @member ReturnRelation
596 @description Summary information for an Return
597 @property {String} number
598 @property {CustomerRelation} customer
599 @property {Date} returnDate
600 @property {Boolean} isPosted
601 @property {Boolean} isOpen
602 @property {Boolean} isVoid
604 it("A model called XM.ReturnRelation extending XM.Info should exist with " +
605 "attributes number (the idAttribute) " +
606 "customer, returnDate, isPosted and isVoid", function () {
607 assert.isFunction(XM.ReturnRelation);
608 var ReturnRelationModel = new XM.ReturnRelation(),
609 attrs = ["number", "customer", "returnDate", "isPosted", "isVoid"];
611 assert.isTrue(ReturnRelationModel instanceof XM.Info);
612 assert.equal(ReturnRelationModel.idAttribute, "number");
613 assert.equal(_.difference(attrs, ReturnRelationModel.getAttributeNames()).length, 0);
616 it("All users with the billing extension may read XV.ReturnRelation.", function () {
617 assert.isTrue(XM.ReturnRelation.canRead());
619 it("XM.ReturnRelation is not editable.", function () {
620 assert.isFalse(XM.ReturnRelation.canCreate());
621 assert.isFalse(XM.ReturnRelation.canUpdate());
622 assert.isFalse(XM.ReturnRelation.canDelete());
627 @description When the customer changes, the billto information should be populated from
628 the customer, along with the salesRep, commission, terms, taxZone, and currency.
629 The billto fields will be read-only if the customer does not allow free-form billto.
631 it("When the customer changes on XM.Return, the following customer data should be " +
632 "populated from the customer: billtoName (= customer.name), billtoAddress1, " +
633 "billtoAddress2, billtoAddress3, billtoCity, billtoState, billtoPostalCode, " +
634 "billtoCountry should be populated by customer.billingContact.address." +
635 "salesRep, commission, taxZone, currency ", function () {
636 assert.isUndefined(returnModel.get("billtoName"));
637 returnModel.set({customer: ttoys});
638 assert.equal(returnModel.get("billtoName"), "Tremendous Toys Incorporated");
639 assert.equal(returnModel.get("billtoAddress2"), "101 Toys Place");
640 assert.isString(returnModel.getValue("salesRep.number"),
641 ttoys.getValue("salesRep.number"));
642 assert.equal(returnModel.getValue("taxZone.code"), "VA TAX");
643 assert.equal(returnModel.getValue("currency.abbreviation"), "USD");
645 it("The following fields will be set to read only if the customer does not allow " +
646 "free form billto: billtoName, billtoAddress1, billtoAddress2, billtoAddress3, " +
647 "billtoCity, billtoState, billtoPostalCode, billtoCountry", function () {
648 assert.isFalse(returnModel.isReadOnly("billtoName"), "TTOYS Name");
649 assert.isFalse(returnModel.isReadOnly("billtoAddress3"), "TTOYS Address3");
650 returnModel.set({customer: vcol});
651 assert.isTrue(returnModel.isReadOnly("billtoName"), "VCOL Name");
652 assert.isTrue(returnModel.isReadOnly("billtoAddress3"), "VCOL Address3");
654 it("If the customer attribute is empty, the above fields should be unset.", function () {
655 assert.isString(returnModel.get("billtoName"));
656 returnModel.set({customer: null});
657 assert.isUndefined(returnModel.get("billtoName"));
658 assert.isUndefined(returnModel.get("billtoAddress2"));
659 assert.isNull(returnModel.get("salesRep"));
660 assert.isNull(returnModel.get("taxZone"));
661 assert.isNull(returnModel.get("currency"));
666 @description The price will be recalculated when the units change.
668 it("If the quantityUnit or sellingUnit are changed, \"calculatePrice\" should be " +
669 "run.", function (done) {
670 returnModel.set({customer: ttoys});
671 assert.isUndefined(lineModel.get("price"));
672 lineModel.set({item: btruck});
673 lineModel.set({credited: 10});
674 setTimeout(function () {
675 assert.equal(lineModel.get("price"), 9.8910);
682 @description If price or credited change, extendedPrice should be recalculated.
684 it("If price or credited change, extendedPrice should be recalculated.", function () {
685 assert.equal(lineModel.get("extendedPrice"), 98.91);
690 @description When credited is changed extendedPrice should be recalculated.
692 it("When credited is changed extendedPrice should be recalculated", function (done) {
693 lineModel.set({credited: 20});
694 setTimeout(function () {
695 assert.equal(lineModel.get("extendedPrice"), 197.82);
702 @description When currency or Return date is changed outstanding credit should be
705 it.skip("When currency or return date is changed outstanding credit should be recalculated",
707 // frustratingly nondeterministic
709 var outstandingCreditChanged = function () {
710 if (returnModel.get("outstandingCredit")) {
711 // second time, with valid currency
712 returnModel.off("change:outstandingCredit", outstandingCreditChanged);
713 assert.equal(returnModel.get("outstandingCredit"), 25250303.25);
716 // first time, with invalid currency
717 returnModel.set({currency: usd});
721 returnModel.on("change:outstandingCredit", outstandingCreditChanged);
722 returnModel.set({currency: null});
727 @description AllocatedCredit should be recalculated when XM.ReturnAllocation records
728 are added or removed.
730 it("AllocatedCredit should be recalculated when XM.ReturnAllocation records " +
731 "are added or removed", function () {
732 assert.isUndefined(returnModel.get("allocatedCredit"));
733 allocationModel.set({currency: usd, amount: 200});
734 returnModel.get("allocations").add(allocationModel);
735 assert.equal(returnModel.get("allocatedCredit"), 200);
740 @description When Return date is changed allocated credit should be recalculated.
742 it("When the Return date is changed allocated credit should be recalculated", function () {
743 allocationModel.set({currency: usd, amount: 300});
744 returnModel.set({returnDate: new Date("1/1/2010")});
745 assert.equal(returnModel.get("allocatedCredit"), 300);
750 @description When subtotal, totalTax or miscCharge are changed, the total
751 should be recalculated.
753 it("When subtotal, totalTax or miscCharge are changed, the total should be recalculated",
755 assert.equal(returnModel.get("total"), 207.71);
756 returnModel.set({miscCharge: 40});
757 assert.equal(returnModel.get("total"), 247.71);
762 @description TotalTax should be recalculated when taxZone changes or
763 taxAdjustments are added or removed.
765 it("TotalTax should be recalculated when taxZone changes.", function (done) {
766 var totalChanged = function () {
767 returnModel.off("change:total", totalChanged);
768 assert.equal(returnModel.get("taxTotal"), 10.88);
769 assert.equal(returnModel.get("total"), 248.70);
773 assert.equal(returnModel.get("taxTotal"), 9.89);
774 returnModel.on("change:total", totalChanged);
775 returnModel.set({taxZone: nctax});
777 it("TotalTax should be recalculated when taxAdjustments are added or removed.",
779 var totalChanged = function () {
780 returnModel.off("change:total", totalChanged);
781 assert.equal(returnModel.get("taxTotal"), 20.88);
782 assert.equal(returnModel.get("total"), 258.70);
786 ReturnTaxModel.set({taxCode: nctaxCode, amount: 10.00});
787 returnModel.on("change:total", totalChanged);
788 returnModel.get("taxAdjustments").add(ReturnTaxModel);
790 it("The document date of the tax adjustment should be the Return date.",
792 assert.equal(returnModel.get("returnDate"), ReturnTaxModel.get("documentDate"));
797 @description When an Return is loaded where "isPosted" is true, then the following
798 attributes will be made read only:
799 lineItems, number, returnDate, terms, salesrep, commission, taxZone, saleType
801 it("When an Return is loaded where isPosted is true, then the following " +
802 "attributes will be made read only: lineItems, number, returnDate, terms, " +
803 "salesrep, commission, taxZone, saleType", function (done) {
804 var postedReturn = new XM.Return(),
805 statusChanged = function () {
806 if (postedReturn.isReady()) {
807 postedReturn.off("statusChange", statusChanged);
808 assert.isTrue(postedReturn.isReadOnly("lineItems"));
809 assert.isTrue(postedReturn.isReadOnly("number"));
810 assert.isTrue(postedReturn.isReadOnly("returnDate"));
811 assert.isTrue(postedReturn.isReadOnly("salesRep"));
812 assert.isTrue(postedReturn.isReadOnly("commission"));
813 assert.isTrue(postedReturn.isReadOnly("taxZone"));
814 assert.isTrue(postedReturn.isReadOnly("saleType"));
819 postedReturn.on("statusChange", statusChanged);
820 postedReturn.fetch({number: "70000"});
825 @description Balance should be recalculated when total, allocatedCredit, or
826 outstandingCredit are changed.
828 it("Balance should be recalculated when total, allocatedCredit, or outstandingCredit " +
829 "are changed", function () {
830 assert.equal(returnModel.get("balance"), 0);
835 @description When allocatedCredit or lineItems exist, currency should become read only.
837 it("When allocatedCredit or lineItems exist, currency should become read only.", function () {
838 assert.isTrue(returnModel.isReadOnly("currency"));
843 @description To save, the Return total must not be less than zero and there must be
844 at least one line item.
846 it("Save validation: The total must not be less than zero", function () {
847 returnModel.set({customer: ttoys, number: "98765"});
848 assert.isUndefined(JSON.stringify(returnModel.validate(returnModel.attributes)));
849 returnModel.set({total: -1});
850 assert.isObject(returnModel.validate(returnModel.attributes));
851 returnModel.set({total: 1});
852 assert.isUndefined(JSON.stringify(returnModel.validate(returnModel.attributes)));
854 it("Save validation: There must be at least one line item.", function () {
855 var lineItems = returnModel.get("lineItems");
856 assert.isUndefined(JSON.stringify(returnModel.validate(returnModel.attributes)));
857 lineItems.remove(lineItems.at(0));
858 assert.isObject(returnModel.validate(returnModel.attributes));
861 it("XM.Return includes a function calculateAuthorizedCredit", function (done) {
862 // TODO test more thoroughly
864 > Makes a call to the server requesting the total authorized credit for a given
866 - in the Return currency
867 - using the Return date for exchange rate conversion.
868 > Authorized credit should only include authoriztions inside the "CCValidDays" window,
869 or 7 days if no CCValidDays is set, relative to the current date.
870 > The result should be set on the authorizedCredit attribute
871 > On response, recalculate the balance (HINT#: Do not attempt to use bindings for this!)
873 assert.isFunction(returnModel.calculateAuthorizedCredit);
874 returnModel.calculateAuthorizedCredit();
875 setTimeout(function () {
876 assert.equal(returnModel.get("authorizedCredit"), 0);
884 @description Return includes a function "calculateTax" that
885 Gathers line item, freight and adjustments
886 Groups by and sums and rounds to XT.MONEY_SCALE for each tax code
887 Sums the sum of each tax code and sets totalTax to the result
889 it.skip("has a calculateTax function that works correctly", function () {
890 // TODO: put under test
895 @description When a customer with non-base currency is selected the following values
896 should be displayed in the foreign currency along with the values in base currency
897 - Unit price, Extended price, Allocated Credit, Authorized Credit, Margin,
898 Subtotal, Misc. Charge, Freight, Total, Balance
901 it.skip("When a customer with non-base currency is selected the following values " +
902 "should be displayed in the foreign currency along with the values in base currency " +
903 " - Unit price, Extended price, Allocated Credit, Authorized Credit, Margin, " +
904 "Subtotal, Misc. Charge, Freight, Total, Balance", function () {
906 // TODO: put under test (requires postbooks demo to have currency conversion)
911 describe("Return List View", function () {
915 @description A list view should exist called XV.ReturnList. Users can perform the following actions from the list: Delete unposted
916 Returns where the user has the MaintainCreditMemos privilege, Post unposted
917 Returns where the user has the "PostARDocuments" privilege, Void posted Returns
918 where the user has the "VoidPostedARCreditMemos" privilege, Print Return forms where
919 the user has the "PrintCreditMemos" privilege.
921 it("Delete unposted Returns where the user has the MaintainCreditMemos privilege",
923 var model = new XM.ReturnListItem();
924 model.couldDestroy(function (response) {
925 assert.isTrue(response);
929 it("Cannot delete Returns that are already posted", function (done) {
930 var model = new XM.ReturnListItem();
931 model.set({isPosted: true});
932 XT.session.privileges.attributes.MaintainCreditMemos = true;
933 model.couldDestroy(function (response) {
934 assert.isFalse(response);
938 it("Post unposted Returns where the user has the PostARDocuments privilege",
940 var model = new XM.ReturnListItem();
941 model.canPost(function (response) {
942 assert.isTrue(response);
946 it("Cannot post Returns that are already posted", function (done) {
947 var model = new XM.ReturnListItem();
948 model.set({isPosted: true});
949 XT.session.privileges.attributes.PostARDocuments = true;
950 model.canPost(function (response) {
951 assert.isFalse(response);
955 it("Void posted Returns where the user has the VoidPostedARCreditMemos privilege",
957 var model = new XM.ReturnListItem();
958 model.set({isPosted: true});
959 XT.session.privileges.attributes.VoidPostedARCreditMemos = true;
960 model.canVoid(function (response) {
961 assert.isTrue(response);
965 it("Cannot void Returns that are not already posted", function (done) {
966 var model = new XM.ReturnListItem();
967 model.set({isPosted: false});
968 XT.session.privileges.attributes.VoidPostedARCreditMemos = true;
969 model.canVoid(function (response) {
970 assert.isFalse(response);
977 @description The Return list should not support multiple selections
979 it("The Return list should not support multiple selections", function () {
980 var list = new XV.ReturnList();
981 assert.isFalse(list.getMultiSelect());
983 it("The Return list has a parameter widget", function () {
985 * The Return list should use a parameter widget that has the following options:
989 - Unposted - checked by default
990 - Posted - unchecked by default
991 - Voided - unchecked by default
995 - Type Pattern (text)
1001 var list = new XV.ReturnList();
1002 assert.isString(list.getParameterWidget());
1007 @description The ReturnList should be printable
1009 it("XV.ReturnList should be printable", function () {
1010 var list = new XV.ReturnList();
1011 // TODO: implement printing on Returns
1012 //assert.isTrue(list.getAllowPrint());
1016 describe("Return workspace", function () {
1017 it("Has a customer relation model that's mapped correctly", function () {
1018 // TODO: generalize this into a relation widget test that's run against
1019 // every relation widget in the app.
1020 var workspace = new XV.ReturnWorkspace();
1021 var widgetAttr = workspace.$.customerWidget.attr;
1022 var attrModel = _.find(XT.session.schemas.XM.attributes.Return.relations,
1023 function (relation) {
1024 return relation.key === widgetAttr;
1026 var widgetModel = XT.getObjectByName(workspace.$.customerWidget.getCollection())
1027 .prototype.model.prototype.recordType;
1028 assert.equal(attrModel, widgetModel);
1033 @description Supports grid-entry of line items on desktop browsers.
1035 it("Should include line items views where a grid box is used for non-touch devices " +
1036 "and a list relation editor for touch devices.", function () {
1039 enyo.platform.touch = true;
1040 workspace = new XV.ReturnWorkspace();
1041 assert.equal(workspace.$.returnLineItemBox.kind, "XV.ReturnLineItemBox");
1042 enyo.platform.touch = false;
1043 workspace = new XV.ReturnWorkspace();
1044 assert.equal(workspace.$.returnLineItemBox.kind, "XV.ReturnLineItemGridBox");
1049 @description A Tax adjustments panel should be available. User shold be able to
1050 add new tax adjustments and remove tax adjustments for unposted Returns
1052 it.skip("Should include a panel that displays a group box of lists of taxes separated" +
1053 "headers for taxes by line items, freight, and adjustments. Users should be " +
1054 "able to add new tax adjustments and remove tax " +
1055 "adjustments for unposted Returns", function () {
1060 @description A Credit Allocation panel should be available. When 'New' button is
1061 selected, user should be allowed to create a minimalized version of cash receipt
1064 describe.skip("Credit Allocation", function () {
1065 it("Should include a panel that displays credit allocations", function () {
1067 it("When clicked a \"new\" button should allow the user to create a new " +
1068 "minimalized version of cash receipt on-the-fly", function () {
1073 @description The cash receipt need only record the amount, currency, document number,
1074 document date, distribution date and whether the balance should generate a
1075 credit memo or a customer deposit, depending on global customer deposit metrics
1077 it("The cash receipt need only record the amount, currency, document number," +
1078 "document date, distribution date and whether the balance should generate a" +
1079 "credit memo or a customer deposit, depending on global" +
1080 "customer deposit metrics", function () {
1085 @description When clicked, an "allocate" button should present a list of open receivables
1086 that are credits that can be associated with the Return
1088 it("When clicked, an \"allocate\" button should present a list of open receivables" +
1089 "that are credits that can be associated with the Return", function () {
1094 @description The 2 buttons above should only be enabled if the user has
1095 the "ApplyARMemos" privilege"
1097 it("The 2 buttons above should only be enabled if the user has" +
1098 "the \"ApplyARMemos\" privilege", function () {
1104 @description The bill to addresses available when searching addresses should filter
1105 on the addresses associated with the customer's account record by default.
1107 it.skip("The bill to addresses available when searching addresses should filter " +
1108 "on the addresses associated with the customer's account record by default.",
1110 // TODO: put under test
1116 @description The customer search list should search only on active customers.
1118 it.skip("The customer search list should search only on active customers", function () {
1119 // TODO: put under test
1125 @description A child workspace view should exist called XV.ReturnLineWorkspace
1126 should include: all the attributes on XM.ReturnLine, item cost and item list
1127 price values, and a read only panel that displays a group box of lists of taxes.
1129 it.skip("The ReturnLine child workspace", function () {
1130 // TODO: put under test
1134 describe("Sales Extension", function () {
1138 @description Return will include authorizedCredit, the sum of credit card authorizations
1139 in the order currency where:
1140 - The current_timestamp - authorization date is less than CCValidDays || 7
1141 - The payment status the cc payment (ccpay) record is authorized ("A")
1142 - The cc payment record is for an order number = the order number specified on
1144 When currency or Return date is changed authorized credit should be recalculated.
1146 it("authorizedCredit", function () {
1147 // TODO: better testing
1148 assert.equal(returnModel.get("authorizedCredit"), 0);
1150 it.skip("When currency or Return date is changed authorized credit should be" +
1151 "recalculated.", function () {
1153 it.skip("When freight is changed the total should be recalculated", function () {
1156 describe("Project extension", function () {
1160 @description The project attribute will be read-only for posted Returns
1162 it.skip("project is read-only for posted Returns", function () {
1163 // TODO: put under test
1169 @description The project widget will be added to the Return workspace if the
1170 UseProjects setting is true.
1172 it.skip("Add the project widget to the Return workspace if the UseProjects setting is true.",
1174 // TODO: put under test
1180 exports.spec = spec;
1181 exports.additionalTests = additionalTests;