diff --git a/Gemfile.lock b/Gemfile.lock index c3fe49d90..71222355d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,8 +14,8 @@ GEM json (>= 1.5.1) atomos (0.1.3) aws-eventstream (1.1.0) - aws-partitions (1.393.0) - aws-sdk-core (3.109.2) + aws-partitions (1.397.0) + aws-sdk-core (3.109.3) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) @@ -23,7 +23,7 @@ GEM aws-sdk-kms (1.39.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.84.1) + aws-sdk-s3 (1.85.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) @@ -93,7 +93,7 @@ GEM faraday_middleware (1.0.0) faraday (~> 1.0) fastimage (2.2.0) - fastlane (2.167.0) + fastlane (2.168.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) aws-sdk-s3 (~> 1.0) diff --git a/app/components/contained-package.js b/app/components/contained-package.js index ef4086b2f..209975669 100644 --- a/app/components/contained-package.js +++ b/app/components/contained-package.js @@ -1,7 +1,6 @@ import Ember from "ember"; -import ItemActionMixin from "stock/mixins/item_actions"; -export default Ember.Component.extend(ItemActionMixin, { +export default Ember.Component.extend({ packageService: Ember.inject.service(), store: Ember.inject.service(), diff --git a/app/components/date-picker.js b/app/components/date-picker.js index d96c10b9b..ad8a76ee0 100644 --- a/app/components/date-picker.js +++ b/app/components/date-picker.js @@ -43,8 +43,8 @@ export default Ember.TextField.extend({ Ember.run.scheduleOnce("afterRender", this, function() { Ember.$(this.element).pickadate({ - selectMonths: !!enablePastDate, - selectYears: !!enablePastDate, + selectMonths: true, + selectYears: true, formatSubmit: "ddd mmm d", monthsFull: moment.months(), monthsShort: moment.monthsShort(), diff --git a/app/components/focus-textfield.js b/app/components/focus-textfield.js index 03e13592e..ed371d286 100644 --- a/app/components/focus-textfield.js +++ b/app/components/focus-textfield.js @@ -1,4 +1,5 @@ import Ember from "ember"; +import { callbackObserver } from "../utils/ember"; export default Ember.TextField.extend({ tagName: "input", @@ -7,19 +8,33 @@ export default Ember.TextField.extend({ cordova: Ember.inject.service(), store: Ember.inject.service(), hasRecentDesignations: true, + autofocus: true, + autofocusOnEmptyValue: true, - triggerAutofocus: Ember.observer("value", function() { - if (this.get("value").length === 0) { - this.$().focus(); + valueAutofocusListener: Ember.observer("value", function() { + if ( + this.get("autofocus") && + this.get("autofocusOnEmptyValue") && + this.get("value").length === 0 + ) { + this.applyFocus(); } }), + autofocusSettingListener: callbackObserver("autofocus", [ + [true, "applyFocus"] + ]), + hasFixedInputHeader: Ember.computed(function() { return ( this.get("cordova").isIOS() && Ember.$(".fixed_search_header").length > 0 ); }), + applyFocus() { + this.$().trigger("focus"); + }, + scrollToStart() { Ember.$(".fixed_search_header").addClass("absolute"); Ember.$(".footer").addClass("absolute_footer"); @@ -38,7 +53,9 @@ export default Ember.TextField.extend({ didInsertElement() { document.body.scrollTop = document.documentElement.scrollTop = 0; - this.$().focus(); + if (this.get("autofocus")) { + this.applyFocus(); + } if (this.get("hasFixedInputHeader")) { this.element.addEventListener("touchstart", this.scrollToStart); } diff --git a/app/components/goodcity/code-search-overlay.js b/app/components/goodcity/code-search-overlay.js index ab40dfa2d..10d8d8a73 100644 --- a/app/components/goodcity/code-search-overlay.js +++ b/app/components/goodcity/code-search-overlay.js @@ -1,6 +1,7 @@ import Ember from "ember"; import _ from "lodash"; import SearchMixin from "stock/mixins/search_resource"; +import AsyncMixin, { ASYNC_BEHAVIOURS } from "stock/mixins/async"; /** * An overlay that pops up from the bottom of the screen, allowing the user @@ -11,8 +12,9 @@ import SearchMixin from "stock/mixins/search_resource"; * @property {boolean} open whether the popup is visible or not * @property {function} onSelect callback triggered when an order is selected */ -export default Ember.Component.extend(SearchMixin, { +export default Ember.Component.extend(SearchMixin, AsyncMixin, { store: Ember.inject.service(), + packageTypeService: Ember.inject.service(), filter: "", searchText: "", fetchMoreResult: true, @@ -22,6 +24,12 @@ export default Ember.Component.extend(SearchMixin, { this._super("code-search-overlay"); }, + async didRender() { + await this.runTask(() => { + return this.get("packageTypeService").preload(); + }, ASYNC_BEHAVIOURS.SILENT_DEPENDENCY); + }, + allPackageTypes: Ember.computed("open", "subsetPackageTypes", function() { if (this.get("subsetPackageTypes")) { return this.get("subsetPackageTypes"); diff --git a/app/components/goodcity/item-search-overlay.js b/app/components/goodcity/item-search-overlay.js index 3f616b030..de82c83d6 100644 --- a/app/components/goodcity/item-search-overlay.js +++ b/app/components/goodcity/item-search-overlay.js @@ -3,6 +3,8 @@ import _ from "lodash"; import config from "stock/config/environment"; import SearchMixin from "stock/mixins/search_resource"; import AsyncMixin from "stock/mixins/async"; +import { chain } from "../../utils/async"; +import { callbackObserver } from "../../utils/ember"; export default Ember.Component.extend(SearchMixin, AsyncMixin, { searchText: "", @@ -12,13 +14,27 @@ export default Ember.Component.extend(SearchMixin, AsyncMixin, { perPage: 10, isMobileApp: config.cordova.enabled, packageService: Ember.inject.service(), + cordova: Ember.inject.service(), messageBox: Ember.inject.service(), i18n: Ember.inject.service(), + requireFocus: false, + + inputmode: Ember.computed("searchMode", function() { + if (this.get("searchMode") === "numeric") { + return "numeric"; + } + return "text"; + }), hasSearchText: Ember.computed("searchText", function() { return !!this.get("searchText"); }), + openStateListener: callbackObserver("open", [ + [true, "onOpen"], + [false, "onClose"] + ]), + closeOverlay() { this.setProperties({ searchText: "", @@ -38,6 +54,20 @@ export default Ember.Component.extend(SearchMixin, AsyncMixin, { return _.flatten([states]).join(","); }, + async onOpen() { + const platform = this.get("cordova"); + const scrollFix = platform.isIOS() || platform.isIOSBrowser(); + + await chain.stagerred([ + () => this.set("requireFocus", true), + () => scrollFix && window.scrollTo(0, 0) + ]); + }, + + onClose() { + this.set("requireFocus", false); + }, + actions: { cancel() { this.closeOverlay(); diff --git a/app/components/goodcity/offers-search-overlay.js b/app/components/goodcity/offers-search-overlay.js index 13ca18b13..7c77cc135 100644 --- a/app/components/goodcity/offers-search-overlay.js +++ b/app/components/goodcity/offers-search-overlay.js @@ -38,7 +38,7 @@ export default Ember.Component.extend(SearchMixin, { companies: true, slug: "search", is_desc: true, - sort_column: "reviewed_at" + sort_column: "received_at" }, this.get("offer_state"), this.getSearchQuery(), diff --git a/app/components/goodcity/remove-item-from-container.js b/app/components/goodcity/remove-item-from-container.js new file mode 100644 index 000000000..0db43b262 --- /dev/null +++ b/app/components/goodcity/remove-item-from-container.js @@ -0,0 +1,58 @@ +import Ember from "ember"; +import _ from "lodash"; +const { getOwner } = Ember; + +import AsyncMixin, { ERROR_STRATEGIES } from "stock/mixins/async"; +import ItemActions from "stock/mixins/item_actions"; + +export default Ember.Component.extend(AsyncMixin, ItemActions, { + packageService: Ember.inject.service(), + locationService: Ember.inject.service(), + + isValidQuantity: Ember.computed( + "maxRemovableQuantity", + "removableQuantity", + function() { + let value = +this.get("removableQuantity"); + return value > 0 && value <= +this.get("maxRemovableQuantity"); + } + ), + + actions: { + async beginUnpack(container, item, quantity) { + this.set("openRemoveItemOverlay", false); + + const selectedLocation = await this.get( + "locationService" + ).userPickLocation(); + + if (!selectedLocation) { + return; + } else { + this.set("removableQuantity", quantity); + this.set("maxRemovableQuantity", quantity); + + this.set("location", selectedLocation); + this.set("openRemoveItemOverlay", true); + } + }, + + async performUnpack() { + await this.runTask(() => { + return this.unpack( + this.get("container"), + this.get("item"), + this.get("location.id"), + this.get("removableQuantity"), + this.get("onUnpackCallback") + ); + }, ERROR_STRATEGIES.MODAL); + + this.set("openRemoveItemOverlay", false); + }, + + cancelAction() { + this.set("openRemoveItemOverlay", false); + } + } +}); diff --git a/app/components/message-box.js b/app/components/message-box.js index 3c30dbfb5..d23f640a5 100644 --- a/app/components/message-box.js +++ b/app/components/message-box.js @@ -11,6 +11,11 @@ export default Ember.Component.extend({ displayCloseLink: false, isVisible: false, + init() { + this._super(...arguments); + this.get("router").addObserver("currentRouteName", () => this.close()); + }, + close() { if (this.get("isVisible")) { this.set("isVisible", false); diff --git a/app/controllers/items/detail.js b/app/controllers/items/detail.js index 9c4972342..d40232c48 100644 --- a/app/controllers/items/detail.js +++ b/app/controllers/items/detail.js @@ -606,8 +606,8 @@ export default GoodcityController.extend( per_page: 10 }) .then(data => { - this.get("store").pushPayload(data); - return data; + this.set("containerQuantity", data.containerQuantity); + return data.containedPackages; }); }, diff --git a/app/controllers/stocktakes/detail.js b/app/controllers/stocktakes/detail.js index cdc16f679..0bbdc034c 100644 --- a/app/controllers/stocktakes/detail.js +++ b/app/controllers/stocktakes/detail.js @@ -19,7 +19,7 @@ const SORTING = { }, BY_INVENTORY_NUM: (rev1, rev2) => { const [n1, n2] = [rev1, rev2] - .map(r => r.getWithDefault("item.inventoryNumber", "0")) + .map(r => r.get("item.inventoryNumber") || "0") .map(inv => inv.replace(/[^0-9]/g, "")) .map(Number); @@ -254,7 +254,13 @@ export default Ember.Controller.extend(AsyncMixin, { * @param {Package} pkg */ async addItem(pkg) { - pkg = pkg || (await this.get("packageService").userPickPackage()); + this.stopScanning(); + + pkg = + pkg || + (await this.get("packageService").userPickPackage({ + searchMode: "numeric" + })); if (!pkg) return; diff --git a/app/controllers/users/contact_details.js b/app/controllers/users/contact_details.js index e8c2808b9..b0e825473 100644 --- a/app/controllers/users/contact_details.js +++ b/app/controllers/users/contact_details.js @@ -12,15 +12,39 @@ export default Ember.Controller.extend( user: Ember.computed.alias("model.user"), userService: Ember.inject.service(), - invalidEmail: Ember.computed("user.email", function() { - const emailRegEx = new RegExp(regex.EMAIL_REGEX); - return this.get("user.email").match(emailRegEx); - }), + isValidEmail(email) { + return regex.EMAIL_REGEX.test(email); + }, - invalidMobile: Ember.computed("mobileNumber", function() { - const hkMobileNumberRegEx = new RegExp(regex.HK_MOBILE_NUMBER_REGEX); - return this.get("mobileNumber").match(hkMobileNumberRegEx); - }), + isValidMobile(mobile) { + return regex.HK_MOBILE_NUMBER_REGEX.test(mobile); + }, + + checkUserEmailValidity(email) { + if (email) { + return this.isValidEmail(email); + } else { + return ( + this.get("user.disabled") || + this.isValidMobile(this.get("mobileNumber")) + ); + } + }, + + checkUserMobileValidity(mobile) { + if (mobile) { + return this.isValidMobile(mobile); + } else { + return ( + this.get("user.disabled") || this.isValidEmail(this.get("user.email")) + ); + } + }, + + hideValidationErrors(target) { + this.set(`${target.id}InputError`, false); + this.set(`${target.id}ValidationError`, false); + }, districts: Ember.computed(function() { return this.get("store") @@ -39,11 +63,12 @@ export default Ember.Controller.extend( updateUserDetails(e) { let value = e.target.value.trim(); let isValid; + if (Object.keys(this.get("user").changedAttributes()).length === 0) { - this.set(`${e.target.id}InputError`, false); - this.set(`${e.target.id}ValidationError`, false); + this.hideValidationErrors(e.target); return; } + switch (e.target.id) { case "firstName": isValid = Boolean(value); @@ -52,24 +77,20 @@ export default Ember.Controller.extend( isValid = Boolean(value); break; case "email": - isValid = value - ? Boolean(this.get("invalidEmail")) - : Boolean(this.get("invalidMobile")); + isValid = this.checkUserEmailValidity(value); break; case "mobile": - isValid = value - ? Boolean(this.get("invalidMobile")) - : Boolean(this.get("invalidEmail")); + isValid = this.checkUserMobileValidity(value); break; } + if (isValid) { this.runTask(async () => { let user = this.get("user"); value = e.target.id == "mobile" && value ? "+852" + value : value; user.set(e.target.id, value); await user.save(); - this.set(`${e.target.id}InputError`, false); - this.set(`${e.target.id}ValidationError`, false); + this.hideValidationErrors(e.target); }, ERROR_STRATEGIES.MODAL); } else { this.get("user").rollbackAttributes(); diff --git a/app/controllers/users/details.js b/app/controllers/users/details.js index 0e386ab4b..ddbbc1fce 100644 --- a/app/controllers/users/details.js +++ b/app/controllers/users/details.js @@ -10,6 +10,7 @@ export default Ember.Controller.extend(OrganisationMixin, AsyncMixin, { user: Ember.computed.alias("model"), disableUserPopupVisible: false, enableUserPopupVisible: false, + updateUserMessagePopupVisible: false, isDisabledUser: Ember.computed.alias("user.disabled"), canDisableUsers: Ember.computed("user.id", function() { @@ -88,6 +89,15 @@ export default Ember.Controller.extend(OrganisationMixin, AsyncMixin, { this.set("enableUserPopupVisible", false); }, + checkUserValidity() { + let user = this.get("user"); + if (user.get("email").length === 0 && user.get("mobile").length === 0) { + this.set("updateUserMessagePopupVisible", true); + } else { + this.send("displayEnableUserPopup"); + } + }, + displayDisableUserPopup() { this.set("disableUserPopupVisible", true); }, diff --git a/app/locales/en/translations.js b/app/locales/en/translations.js index be4d92f7a..02a2e6c2c 100644 --- a/app/locales/en/translations.js +++ b/app/locales/en/translations.js @@ -135,7 +135,12 @@ export default { "Added quantity cannot be greater than the available quantity for each location.", type_to_search: "Type in to search items to add.", cannot_change_type: - "Cannot change type of a box with items. Please remove the items and try again" + "Cannot change type of a box with items. Please remove the items and try again", + remove_from: "Remove from", + item: "Item", + from: "From", + to_location: "To Location", + max: "Max" }, messages: { you: "You", @@ -1062,7 +1067,10 @@ export default { "Note: If you do not wish this user to have same rights to access apps or act on behalf of charities as they had before, please modify their access.", enable_user: "Re-enable User", re_enable: "Re-enable", - account_disabled: "Account Disabled" + account_disabled: "Account Disabled", + warning: "Warning!", + missing_user_details_warning: + "This user does not have valid email and mobile number. Please update user details from contact details page before re-enabling." }, contact_details: { diff --git a/app/locales/zh-tw/translations.js b/app/locales/zh-tw/translations.js index 35bff70a0..35ac0a1c4 100644 --- a/app/locales/zh-tw/translations.js +++ b/app/locales/zh-tw/translations.js @@ -131,7 +131,12 @@ export default { invalid_quantity: "已增加的份量不可多於每個位置的可用的份量", type_to_search: "输入搜索要添加的项目。", cannot_change_type: - "Cannot change type of a box with items. Please remove the items and try again" + "Cannot change type of a box with items. Please remove the items and try again", + remove_from: "Remove from", + item: "Item", + from: "From", + to_location: "To Location", + max: "Max" }, messages: { you: "您", @@ -1015,7 +1020,10 @@ export default { "注意:如你不希望此用戶保留舊有權限或慈善機構員工身份,請變更他的存取權限。", enable_user: "重新啟用用戶", re_enable: "重新啟用", - account_disabled: "帳號已停用" + account_disabled: "帳號已停用", + warning: "Warning!", + missing_user_details_warning: + "This user does not have valid email and mobile number. Please update user details from contact details page before re-enabling." }, contact_details: { diff --git a/app/mixins/async.js b/app/mixins/async.js index c8c957f87..2ba45bbf9 100644 --- a/app/mixins/async.js +++ b/app/mixins/async.js @@ -49,6 +49,10 @@ export const ASYNC_BEHAVIOURS = { LOUD: { showSpinner: true, errorStrategy: ERROR_STRATEGIES.MODAL + }, + SILENT_DEPENDENCY: { + showSpinner: false, + errorStrategy: ERROR_STRATEGIES.MODAL } }; diff --git a/app/mixins/item_actions.js b/app/mixins/item_actions.js index 23fb40e37..ee5cb10e4 100644 --- a/app/mixins/item_actions.js +++ b/app/mixins/item_actions.js @@ -112,7 +112,7 @@ export default Ember.Mixin.create(AsyncMixin, { * @param Integer quantity * @param function callback */ - async _unpack(container, item, location_id, quantity, callback) { + async unpack(container, item, location_id, quantity, callback) { if (!item) { throw new Error(this.get("i18n").t("box_pallet.bad_item")); } @@ -212,32 +212,6 @@ export default Ember.Mixin.create(AsyncMixin, { this.get("actionTarget"), this.get("actionName") ); - }, - - /** - * Unpack the requested quantity from a container (either box or pallet) - * and invoke any callback passed as argument. - * @param EmberObject container - * @param EmberObject item - * @param Integer quantity - * @param function callback - */ - async unpack(container, item, quantity, callback) { - const selectedLocation = await this.get( - "locationService" - ).userPickLocation(); - - if (!selectedLocation) { - return; - } - - await this._unpack( - container, - item, - selectedLocation.id, - quantity, - callback - ); } } }); diff --git a/app/mixins/preload_data.js b/app/mixins/preload_data.js index 908e281c3..643ebbce4 100644 --- a/app/mixins/preload_data.js +++ b/app/mixins/preload_data.js @@ -21,10 +21,6 @@ export default Ember.Mixin.create({ this.notifyPropertyChange("session.currentUser"); }) ); - promises = promises.concat(this.store.query("code", { stock: true })); - promises = promises.concat( - this.store.query("cancellation_reason", { for: "order" }) - ); promises = promises.concat(retrieve(config.APP.PRELOAD_TYPES)); promises.push(this.get("messages").fetchUnreadMessageCount()); } diff --git a/app/models/designation.js b/app/models/designation.js index 8fa9382e6..a264e38fa 100644 --- a/app/models/designation.js +++ b/app/models/designation.js @@ -111,13 +111,6 @@ export default Model.extend({ return this.get("items").rejectBy("sentOn", null); }), - canReopen: Ember.computed("state", function() { - return ( - this.get("isClosed") && - (this.get("isGoodCityOrder") || this.get("isShipmentOrder")) - ); - }), - capitalizedState: Ember.computed("state", function() { return this.get("state").capitalize(); }), diff --git a/app/models/user.js b/app/models/user.js index c8468add1..5395aa1c0 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -60,7 +60,9 @@ export default Addressable.extend({ }), fullName: Ember.computed("firstName", "lastName", function() { - return this.get("firstName") + " " + this.get("lastName"); + // To avoid the undefined undefined in case record is not loaded yet + const [firstName, lastName] = [this.get("firstName"), this.get("lastName")]; + return `${firstName ? firstName : ""} ${lastName ? lastName : ""}`; }), organisations: Ember.computed("organisations_users_ids", function() { diff --git a/app/routes/index.js b/app/routes/index.js index 58494e9ec..b5de7f3c1 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -6,14 +6,11 @@ export default SessionRoute.extend({ model() { let canViewDashboard = this.get("session.currentUser.canManageOrders"); var recentlyUsedDesignations = this.get("store").query("designation", { - recently_used: true - }); - var recentlyUsedLocations = this.get("store").query("location", { - recently_used: true + recently_used: true, + shallow: true }); this.get("store").pushPayload(recentlyUsedDesignations); - this.get("store").pushPayload(recentlyUsedLocations); if (canViewDashboard) { return Ember.RSVP.hash({ diff --git a/app/routes/items/detail.js b/app/routes/items/detail.js index 6dac7baf0..1428e856c 100644 --- a/app/routes/items/detail.js +++ b/app/routes/items/detail.js @@ -38,6 +38,8 @@ export default AuthorizeRoute.extend({ } ); } + await this.store.findAll("restriction", { reload: true }); + await this.store.findAll("donor_condition", { reload: true }); }, beforeModel(transition) { @@ -69,6 +71,7 @@ export default AuthorizeRoute.extend({ controller.set("active", true); controller.set("showExtendedFooterMenu", false); controller.set("displayResults", true); + controller.set("containerQuantity", null); const defaultValue = await this.get("packageService").getItemValuation({ donorConditionId: model.get("donorCondition.id"), diff --git a/app/routes/items/new.js b/app/routes/items/new.js index 28872dd36..451a4a63d 100644 --- a/app/routes/items/new.js +++ b/app/routes/items/new.js @@ -47,6 +47,10 @@ export default AuthorizeRoute.extend(GradeMixin, { this.store.findAll("location", { reload: true }); + this.store.findAll("restriction", { + reload: true + }); + this.store.findAll("donor_condition", { reload: true }); }, setupPrinterId(controller) { diff --git a/app/routes/orders/detail.js b/app/routes/orders/detail.js index df1242618..6412bd3cf 100644 --- a/app/routes/orders/detail.js +++ b/app/routes/orders/detail.js @@ -7,5 +7,9 @@ export default AuthorizeRoute.extend({ model({ order_id }) { return this.loadIfAbsent("designation", order_id); + }, + + async afterModel() { + await this.store.query("cancellation_reason", { for: "order" }); } }); diff --git a/app/routes/orders/order_types.js b/app/routes/orders/order_types.js index 9bf224276..96a60c43d 100644 --- a/app/routes/orders/order_types.js +++ b/app/routes/orders/order_types.js @@ -7,13 +7,7 @@ import Ember from "ember"; export default detail.extend({ processingChecklist: Ember.inject.service(), - loadLookups: Cache.cached(function() { - const load = modelName => { - return this.store - .findAll(modelName) - .then(data => this.store.pushPayload(data)); - }; - + loadLookups() { // Load dependent lookup tables return Ember.RSVP.all( [ @@ -21,9 +15,13 @@ export default detail.extend({ "gogovan_transport", "booking_type", "process_checklist" - ].map(load) + ].map(model => + !this.store.peekAll(model).get("length") + ? this.store.findAll(model) + : this.store.peekAll(model) + ) ); - }), + }, loadDependencies(order) { return Ember.RSVP.all([ @@ -35,7 +33,7 @@ export default detail.extend({ async afterModel(model) { await this._super(...arguments); - await Ember.RSVP.all([this.loadLookups(), this.loadDependencies(model)]); + await Promise.all([this.loadLookups(), this.loadDependencies(model)]); }, setupController(controller, model) { diff --git a/app/routes/stocktakes/detail.js b/app/routes/stocktakes/detail.js index 3091e9178..ce6d2b9c4 100644 --- a/app/routes/stocktakes/detail.js +++ b/app/routes/stocktakes/detail.js @@ -2,8 +2,11 @@ import AuthorizeRoute from "../authorize"; import Ember from "ember"; export default AuthorizeRoute.extend({ + packageTypeService: Ember.inject.service(), + model({ stocktake_id }) { return Ember.RSVP.hash({ + codes: this.get("packageTypeService").preload(), stocktake: this.store.findRecord("stocktake", stocktake_id) }); }, diff --git a/app/routes/stocktakes/index.js b/app/routes/stocktakes/index.js index 173c45493..4d329d101 100644 --- a/app/routes/stocktakes/index.js +++ b/app/routes/stocktakes/index.js @@ -2,8 +2,11 @@ import AuthorizeRoute from "../authorize"; import Ember from "ember"; export default AuthorizeRoute.extend({ + packageTypeService: Ember.inject.service(), + model() { return Ember.RSVP.hash({ + codes: this.get("packageTypeService").preload(), stocktakes: this.store.findAll("stocktake", { reload: true }) }); }, diff --git a/app/services/barcode-service.js b/app/services/barcode-service.js index 03275dd6b..7ac42dc39 100644 --- a/app/services/barcode-service.js +++ b/app/services/barcode-service.js @@ -158,6 +158,7 @@ export default Ember.Service.extend({ overlay.destroy(); capture.removeListener(listener); Ember.run.debounce(this, this.__disableScan, SCAN_DELAY + 100); + this.turnFlashlightOff(); onStop(); }); @@ -172,6 +173,41 @@ export default Ember.Service.extend({ // Service API // ---------------------- + /** + * Turns the flashlight on (if available) + * + * @returns {Promise} + */ + turnFlashlightOn() { + if (this.enabled()) { + this.__getCamera().desiredTorchState = "on"; + this.set("flashlightActive", true); + } + }, + + /** + * Turns the flashlight off (if available) + * + * @returns {Promise} + */ + turnFlashlightOff() { + if (this.enabled()) { + this.__getCamera().desiredTorchState = "off"; + this.set("flashlightActive", false); + } + }, + + /** + * Toggles the flashlight status + * + * @returns {Promise} + */ + toggleFlashlight() { + return this.get("flashlightActive") + ? this.turnFlashlightOff() + : this.turnFlashlightOn(); + }, + /** * Returns true if scanning is possible * diff --git a/app/services/cordova.js b/app/services/cordova.js index 68a47e9d5..7486efc9d 100644 --- a/app/services/cordova.js +++ b/app/services/cordova.js @@ -16,6 +16,11 @@ export default Ember.Service.extend({ ); }, + isIOSBrowser() { + const userAgent = window.navigator.userAgent; + return userAgent.match(/iPad/i) || userAgent.match(/iPhone/i); + }, + isIOS() { if (!config.cordova.enabled || !window.device) { return; diff --git a/app/services/package-service.js b/app/services/package-service.js index bb4222a96..c63c2f048 100644 --- a/app/services/package-service.js +++ b/app/services/package-service.js @@ -114,7 +114,8 @@ export default ApiBaseService.extend(NavigationAwareness, { pagination ); this.get("store").pushPayload(data); - return Promise.all( + let containerQuantity = data.meta.total_count; + let containedPackages = await Promise.all( data.items.map(async item => { const quantity = await this.fetchAddedQuantity(boxPalletId, item.id); return { @@ -124,6 +125,7 @@ export default ApiBaseService.extend(NavigationAwareness, { }; }) ); + return { containedPackages, containerQuantity }; }, async fetchParentContainers(pkg, opts = {}) { diff --git a/app/services/package-type-service.js b/app/services/package-type-service.js index 0ec30a469..3c8134a2f 100644 --- a/app/services/package-type-service.js +++ b/app/services/package-type-service.js @@ -1,5 +1,6 @@ import Ember from "ember"; import _ from "lodash"; +import { cached } from "../utils/cache"; // @todo: unify code and pacakge_type under 'package_type' const MODEL_NAME = "code"; @@ -40,6 +41,10 @@ export default Ember.Service.extend({ return deferred.promise; }, + preload: cached(async function() { + return await this.get("store").query("code", { stock: true }); + }), + parentsOf(packageType) { const hierarchy = this.get("hierarchy"); const code = packageType.get("code"); diff --git a/app/styles/templates/items/detail/_item_actions_overlay.scss b/app/styles/templates/items/detail/_item_actions_overlay.scss index 79a44833e..6e91ec78f 100644 --- a/app/styles/templates/items/detail/_item_actions_overlay.scss +++ b/app/styles/templates/items/detail/_item_actions_overlay.scss @@ -1,5 +1,5 @@ .dispatch-form.package-actions-form { - + .order-preview { .row.gc-orders-package-block { margin: 5rem auto 1rem auto; @@ -46,6 +46,7 @@ .warn-text { color: red; + margin-left: 1rem; } .qty-input { @@ -84,4 +85,4 @@ } } } -} \ No newline at end of file +} diff --git a/app/styles/templates/stocktakes/_detail.scss b/app/styles/templates/stocktakes/_detail.scss index 30b957aa4..a186c23b2 100644 --- a/app/styles/templates/stocktakes/_detail.scss +++ b/app/styles/templates/stocktakes/_detail.scss @@ -94,7 +94,9 @@ margin-bottom: 1rem; button { - @include ellipsis(); + &.ellipsis { + @include ellipsis(); + } height: 2.5rem; } @@ -253,4 +255,4 @@ } } } -} \ No newline at end of file +} diff --git a/app/templates/_global_components.hbs b/app/templates/_global_components.hbs index 141d97f9d..05a43b72e 100644 --- a/app/templates/_global_components.hbs +++ b/app/templates/_global_components.hbs @@ -53,6 +53,7 @@ open=packageService.openPackageSearch associatedPackageTypes=packageService.packageSearchOptions.packageTypes storageTypeName=packageService.packageSearchOptions.storageTypeName + searchMode=packageService.packageSearchOptions.searchMode onConfirm=packageService.onPackageSelected }} diff --git a/app/templates/components/contained-package.hbs b/app/templates/components/contained-package.hbs index b6c3a6394..a12ac3b39 100644 --- a/app/templates/components/contained-package.hbs +++ b/app/templates/components/contained-package.hbs @@ -18,11 +18,14 @@ {{#unless disableRemove}} -
-
- {{fa-icon 'times-circle' size="lg"}} {{t 'box_pallet.remove'}} -
-
+ {{#goodcity/remove-item-from-container + item=package + container=entity + addedQuantity=pkg.addedQuantity + onUnpackCallback=onUnpack + }} + {{fa-icon 'times-circle' size="lg"}} {{t 'box_pallet.remove'}} + {{/goodcity/remove-item-from-container}} {{/unless}} diff --git a/app/templates/components/goodcity/item-search-overlay.hbs b/app/templates/components/goodcity/item-search-overlay.hbs index a9d60afe7..9d861d3cc 100644 --- a/app/templates/components/goodcity/item-search-overlay.hbs +++ b/app/templates/components/goodcity/item-search-overlay.hbs @@ -8,6 +8,8 @@
{{focus-textfield name="search-text" + autofocus=requireFocus + inputmode=inputmode placeholder=(t "search_min") value=searchText }} {{#if hasSearchText}} diff --git a/app/templates/components/goodcity/remove-item-from-container.hbs b/app/templates/components/goodcity/remove-item-from-container.hbs new file mode 100644 index 000000000..8adc25cae --- /dev/null +++ b/app/templates/components/goodcity/remove-item-from-container.hbs @@ -0,0 +1,63 @@ +
+
+ {{yield}} +
+
+ +{{#popup-overlay open=openRemoveItemOverlay}} +
+ +
+ {{package-summary-block + isRedirectable=false + model=item + disableLink=true + classNames="package-summary-block-view"}} +
+ + {{#form-control}} +
+
{{fa-icon 'box-open'}} {{t "box_pallet.remove_from"}} {{container.storageTypeName}}
+
+
+
{{t "box_pallet.item"}}
+
{{fa-icon 'tag'}} {{item.inventoryNumber}}
+
+ +
+
{{t "box_pallet.from"}} {{container.storageTypeName}}
+
{{fa-icon 'box-open'}} {{container.inventoryNumber}}
+
+ +
+
{{t "box_pallet.to_location"}}
+
+ {{fa-icon 'map-marker-alt'}} + {{location.displayName}} +
+
+ +
+
{{t "box_pallet.quantity"}}
+
+ {{numeric-input name="removableQuantity" value=removableQuantity maxlength="5" pattern="^[1-9][0-9]*$"}} +
+
+ {{t "box_pallet.max"}}: {{maxRemovableQuantity}} +
+
+
+
+ +
+
{{t "cancel"}}
+ {{#if isValidQuantity }} +
{{t "box_pallet.remove"}} ({{ removableQuantity }})
+ {{else}} +
{{t "box_pallet.remove"}} ({{ removableQuantity }})
+ {{/if}} +
+ {{/form-control}} + +
+{{/popup-overlay}} diff --git a/app/templates/components/package-summary-block.hbs b/app/templates/components/package-summary-block.hbs index c213ec1f4..bc85a0275 100644 --- a/app/templates/components/package-summary-block.hbs +++ b/app/templates/components/package-summary-block.hbs @@ -101,9 +101,14 @@
{{addedQuantityCount}}
-
+ {{#goodcity/remove-item-from-container + item=item + container=model + addedQuantity=addedQuantityCount + onUnpackCallback=onUnpack + }} {{fa-icon 'times-circle' size="lg"}} {{t 'box_pallet.remove'}} -
+ {{/goodcity/remove-item-from-container}}
diff --git a/app/templates/index.hbs b/app/templates/index.hbs index e322245ed..5fe5cf360 100644 --- a/app/templates/index.hbs +++ b/app/templates/index.hbs @@ -107,7 +107,9 @@ {{ order.code }}
- {{order.createdBy.fullName}} + {{#if order.createdBy.fullName}} + {{order.createdBy.fullName}} + {{/if}}
diff --git a/app/templates/items/detail/tabs/_sub_tabs.hbs b/app/templates/items/detail/tabs/_sub_tabs.hbs index 627123cd0..66b9b83b8 100644 --- a/app/templates/items/detail/tabs/_sub_tabs.hbs +++ b/app/templates/items/detail/tabs/_sub_tabs.hbs @@ -1,7 +1,7 @@
- {{item.storageTypeName}} {{t 'box_pallet.content'}} + {{item.storageTypeName}} {{t 'box_pallet.content'}} ({{containerQuantity}})
diff --git a/app/templates/items/detail/tabs/storage_content.hbs b/app/templates/items/detail/tabs/storage_content.hbs index 1e472b564..0a709eeb7 100644 --- a/app/templates/items/detail/tabs/storage_content.hbs +++ b/app/templates/items/detail/tabs/storage_content.hbs @@ -1,4 +1,5 @@ {{partial 'items/detail/tabs/_sub_tabs' }} +
+
    diff --git a/app/templates/orders/_international_orders_summary.hbs b/app/templates/orders/_international_orders_summary.hbs index 76232cb0a..310c66266 100644 --- a/app/templates/orders/_international_orders_summary.hbs +++ b/app/templates/orders/_international_orders_summary.hbs @@ -57,14 +57,12 @@ {{t "order.international.shipment_date"}}
- {{calendar-input + {{date-picker name='order_selection_date' class="inline-edit-bg" id='shipmentDate' - formatSubmit="dd mmmm yyyy" - onSelect=(action "updateShipmentDate") - enablePastDate=true - selection=model.shipmentDate + model=model.shipmentDate + onSelect=(action 'updateShipmentDate') }}
diff --git a/app/templates/orders/_order_status_bar.hbs b/app/templates/orders/_order_status_bar.hbs index d3f1890ee..3df5b0907 100644 --- a/app/templates/orders/_order_status_bar.hbs +++ b/app/templates/orders/_order_status_bar.hbs @@ -1,5 +1,4 @@
- {{#if (is-or model.isGoodCityOrder model.isShipmentOrder)}}
@@ -63,14 +62,4 @@
{{/if}}
- {{else}} -
-
- {{fa-icon order.transportIcon}}{{fa-icon 'lg'}} {{ order.transportLabel }} -
-
- {{t "order_details.submitted"}} {{display-datetime model.submittedAt format="DD MMM 'YY"}} -
-
- {{/if}}
diff --git a/app/templates/orders/_review_order_options.hbs b/app/templates/orders/_review_order_options.hbs index a0af117dd..31f1eec1d 100644 --- a/app/templates/orders/_review_order_options.hbs +++ b/app/templates/orders/_review_order_options.hbs @@ -1,7 +1,7 @@ {{fa-icon 'ellipsis-v'}} \ No newline at end of file +
diff --git a/app/templates/users/_disable_user_popup.hbs b/app/templates/users/_disable_user_popup.hbs index 37d1acc13..8d7c8d9ab 100644 --- a/app/templates/users/_disable_user_popup.hbs +++ b/app/templates/users/_disable_user_popup.hbs @@ -39,3 +39,20 @@ {{/message-box}} {{!-- END --}} + +{{!-- UPDATE USER DETAILS POPUP --}} +{{#message-box + btn1Text=(t "ok") + btn1Callback=(action "cancelEnableUser") + isVisible=updateUserMessagePopupVisible + classNames="popupOverlay ui disable-user-popup" +}} + +
{{t "users.details.warning"}}
+ +
+ {{t "users.details.missing_user_details_warning"}} +
+ +{{/message-box}} +{{!-- END --}} diff --git a/app/templates/users/details.hbs b/app/templates/users/details.hbs index b127daf18..afc5d5389 100644 --- a/app/templates/users/details.hbs +++ b/app/templates/users/details.hbs @@ -22,7 +22,7 @@ {{#if showEnableUserMessage}} {{/if}} @@ -114,7 +114,7 @@ {{#if canDisableUsers}}