From 120a8cb90894e542a73b879f5a3edf3328851069 Mon Sep 17 00:00:00 2001 From: mdulko Date: Tue, 24 Sep 2024 11:41:00 +0200 Subject: [PATCH 1/5] OLMIS-7987: Move Submit Requisitionless Orders functionalities from Angola to Core instance --- CHANGELOG.md | 5 + src/requisition-order-create/messages_en.json | 37 +- .../order-create-form-helper-functions.jsx | 93 ++++ .../order-create-form.jsx | 178 +++----- .../order-create-page.jsx | 54 ++- .../order-create-requisition-info.jsx | 36 ++ .../order-create-summary-modal.jsx | 67 +++ .../order-create-tab.jsx | 141 ++++++ .../order-create-table-helper-functions.jsx | 78 ++++ .../order-create-table.jsx | 430 +++++------------- ...der-create-validation-helper-functions.jsx | 45 ++ .../order-create.constant.jsx | 81 ++++ .../order-create.scss | 53 ++- src/select-products-modal/messages_en.json | 14 + .../select-products-modal-state.provider.js | 124 +++++ ...lect-products-modal-state.provider.spec.js | 223 +++++++++ .../select-products-modal.controller.js | 114 +++++ .../select-products-modal.controller.spec.js | 278 +++++++++++ .../select-products-modal.html | 84 ++++ .../select-products-modal.module.js | 36 ++ .../select-products-modal.service.js | 135 ++++++ .../select-products-modal.service.spec.js | 122 +++++ 22 files changed, 1970 insertions(+), 458 deletions(-) create mode 100644 src/requisition-order-create/order-create-form-helper-functions.jsx create mode 100644 src/requisition-order-create/order-create-requisition-info.jsx create mode 100644 src/requisition-order-create/order-create-summary-modal.jsx create mode 100644 src/requisition-order-create/order-create-tab.jsx create mode 100644 src/requisition-order-create/order-create-table-helper-functions.jsx create mode 100644 src/requisition-order-create/order-create-validation-helper-functions.jsx create mode 100644 src/requisition-order-create/order-create.constant.jsx create mode 100644 src/select-products-modal/messages_en.json create mode 100644 src/select-products-modal/select-products-modal-state.provider.js create mode 100644 src/select-products-modal/select-products-modal-state.provider.spec.js create mode 100644 src/select-products-modal/select-products-modal.controller.js create mode 100644 src/select-products-modal/select-products-modal.controller.spec.js create mode 100644 src/select-products-modal/select-products-modal.html create mode 100644 src/select-products-modal/select-products-modal.module.js create mode 100644 src/select-products-modal/select-products-modal.service.js create mode 100644 src/select-products-modal/select-products-modal.service.spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e5fee73..fb8b255d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +7.0.13-SNAPSHOT / WIP +================== +New functionalities that are backwards-compatible: +* [OLMIS-7987](https://openlmis.atlassian.net/browse/OLMIS-7987): Move Submit Requisitionless Orders functionalities from Angola to Core instance + 7.0.12 / 2024-04-19 ================== New functionalities that are backwards-compatible: diff --git a/src/requisition-order-create/messages_en.json b/src/requisition-order-create/messages_en.json index 66153cb4..330c22e9 100644 --- a/src/requisition-order-create/messages_en.json +++ b/src/requisition-order-create/messages_en.json @@ -1,8 +1,39 @@ { - "requisition.orderCreate": "Create Order", - "requisition.orderCreate.submitted": "Order created successfully", + "requisition.orderCreate": "Create Orders", + "requisition.orderCreate.cancel": "Cancel", + "requisition.orderCreate.loading": "Loading", + "requisition.orderCreate.view": "View", + "requisition.orderCreate.printOrder": "Print Order", + "requisition.orderCreate.edit": "Edit", + "requisition.orderCreate.create": "Create Order", + "requisition.orderCreate.saveDraft": "Save Draft", + "requisition.orderCreate.submitted": "Orders created successfully", "requisition.orderCreate.createdOrderSent.success": "Offline created order sent successfully.", "requisition.orderCreate.createdOrderSent.error": "Error occurred while sending offline created order.", "requisition.orderCreate.draftUpdate.success": "Draft order update success.", - "requisition.orderCreate.draftUpdate.error": "Error occurred while updating draft order." + "requisition.orderCreate.draftUpdate.error": "Error occurred while updating draft order.", + "requisition.orderCreate.delete": "Delete", + "requisition.orderCreate.deleteBatch": "Delete Batch", + "requisition.orderCreate.delete.prompt": "Are you sure you want to delete this order?", + "requisition.orderCreate.delete.prompt.batch": "Are you sure you want to delete available orders?", + "requisition.orderCreate.delete.error": "Error occurred while deleting order(s).", + "requisition.orderCreate.delete.success": "Order(s) deleted successfully.", + "requisition.orderCreate.program": "Program", + "requisition.orderCreate.program.placeholder": "Select Program", + "requisition.orderCreate.reqFacility": "Requesting Facility", + "requisition.orderCreate.reqFacility.placeholder": "Select Requesting Facility", + "requisition.orderCreate.supFacility": "Supplying Facility", + "requisition.orderCreate.supFacility.placeholder": "Select Supplying Facility", + "requisition.orderCreate.table.productCode": "Product Code", + "requisition.orderCreate.table.product": "Product", + "requisition.orderCreate.table.soh": "SOH", + "requisition.orderCreate.table.quantity": "Quantity", + "requisition.orderCreate.table.actions": "Actions", + "requisition.orderCreate.table.facility": "Facility", + "requisition.orderCreate.table.addProduct": "Add", + "requisition.orderCreate.table.productAlreadyAdded": "This product was already added to the table", + "requisition.orderCreate.requisistionInfo.status": "Status", + "requisition.orderCreate.requisistionInfo.dateCreated": "Date Created", + "requisition.orderCreate.searchSelect.placeholder": "Select an option", + "requisition.orderCreate.searchSelect.empty.message": "Not found" } diff --git a/src/requisition-order-create/order-create-form-helper-functions.jsx b/src/requisition-order-create/order-create-form-helper-functions.jsx new file mode 100644 index 00000000..dee9c98b --- /dev/null +++ b/src/requisition-order-create/order-create-form-helper-functions.jsx @@ -0,0 +1,93 @@ +export const getMappedRequestingFacilities = (facilities, userId, selectedProgram, selectedSupplyingFacility) => { + return facilities.map((facilityId) => ({ + emergency: true, + createdBy: { id: userId }, + program: { id: selectedProgram }, + requestingFacility: { id: facilityId }, + receivingFacility: { id: facilityId }, + supplyingFacility: { id: selectedSupplyingFacility }, + facility: { id: facilityId } + })); +}; + +export const goToOrderEdit = (orders, orderService, history) => { + const orderCreationPromises = orders.map(order => orderService.create(order)); + Promise.all(orderCreationPromises).then((createdOrders) => { + const ordersIds = createdOrders.map(order => order.id).join(','); + history.push(`/requisitions/orderCreate/${ordersIds}`); + }); +}; + +const getSupervisoryNodes = (selectedRequestingFacilities, selectedProgram, supervisoryNodeResource) => { + const supervisoryNodeResourcePromisses = selectedRequestingFacilities.map(facilityId => supervisoryNodeResource.query({ + programId: selectedProgram, + facilityId: facilityId + })); + + return Promise.all(supervisoryNodeResourcePromisses); +} + +const assignNodeToArray = (acc, obj, mappedPages) => { + if (!acc.some(existingObj => existingObj.id === obj.id)) { + if (mappedPages.every(page => page.some(item => item.id === obj.id))) { + acc.push(obj); + } + } +}; + +const getNodesValue = (pages) => { + const mappedPages = pages.map(page => page.content); + const nodes = mappedPages.reduce((acc, arr) => { + arr.forEach(obj => { + assignNodeToArray(acc, obj, mappedPages); + }); + return acc; + }, []); + return nodes; +} + +const getSupplyLines = (nodes, supplyLineResource, selectedProgram) => { + return Promise.all(nodes.map((node) => ( + supplyLineResource.query({ + programId: selectedProgram, + supervisoryNodeId: node.id + }) + ))) +} + +const setSupplyingFacilities = (supplyLinesResources, facilityService, setSupplyingFacilityOptions) => { + const supplyLines = _.flatten(supplyLinesResources.map((it) => (it.content))); + const facilityIds = _.uniq(supplyLines.map((it) => (it.supplyingFacility.id))); + + if (facilityIds.length > 0) { + facilityService.query({ + id: facilityIds + }) + .then((resp) => { + const facilities = resp.content; + setSupplyingFacilityOptions(facilities.map(facility => ({ name: facility.name, value: facility.id }))); + }); + } else { + setSupplyingFacilityOptions([]); + } +} + +export const updateSupplyingFacilitiesValue = (selectedProgram, selectedRequestingFacilities, supervisoryNodeResource, supplyLineResource, facilityService, setSupplyingFacilityOptions) => { + if (selectedProgram && selectedRequestingFacilities) { + getSupervisoryNodes(selectedRequestingFacilities, selectedProgram, supervisoryNodeResource) + .then((pages) => { + const nodes = getNodesValue(pages); + + if (nodes.length > 0) { + getSupplyLines(nodes, supplyLineResource, selectedProgram) + .then((supplyLinesResources) => { + setSupplyingFacilities(supplyLinesResources, facilityService, setSupplyingFacilityOptions); + }); + } else { + setSupplyingFacilityOptions([]); + } + }); + } else { + setSupplyingFacilityOptions([]); + } +} \ No newline at end of file diff --git a/src/requisition-order-create/order-create-form.jsx b/src/requisition-order-create/order-create-form.jsx index 057246dc..8f593903 100644 --- a/src/requisition-order-create/order-create-form.jsx +++ b/src/requisition-order-create/order-create-form.jsx @@ -17,6 +17,10 @@ import React, { useState, useEffect, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; import getService from '../react-components/utils/angular-utils'; import { SearchSelect } from './search-select'; +import EditableTable from '../react-components/table/editable-table'; +import { getMappedRequestingFacilities, goToOrderEdit, updateSupplyingFacilitiesValue } from './order-create-form-helper-functions'; +import { orderCreateFormTableColumns } from './order-create.constant'; + const OrderCreateForm = () => { @@ -25,62 +29,64 @@ const OrderCreateForm = () => { const [programOptions, setProgramOptions] = useState([]); const [requestingFacilityOptions, setRequestingFacilityOptions] = useState([]); const [supplyingFacilityOptions, setSupplyingFacilityOptions] = useState([]); - const [selectedProgram, selectProgram] = useState(''); - const [selectedRequestingFacility, selectRequestingFacility] = useState(''); - const [selectedSupplyingFacility, selectSupplyingFacility] = useState(''); + const [selectedProgram, setSelectedProgram] = useState(''); + const [selectedRequestingFacilities, setSelectedRequestingFacilities] = useState([]); + const [selectedSupplyingFacility, setSelectedSupplyingFacility] = useState(''); + const [filteredRequestingFacilities, setFilteredRequestingFacilities] = useState([]); - const ADMINISTRATION_RIGHTS = useMemo( - () => { - return getService('ADMINISTRATION_RIGHTS'); - }, - [] - ); + const ADMINISTRATION_RIGHTS = useMemo(() => getService('ADMINISTRATION_RIGHTS'), []); + const programService = useMemo(() => getService('programService'), []); + const facilityService = useMemo(() => getService('facilityService'), []); + const orderService = useMemo(() => getService('orderCreateService'), []); + const columns = useMemo(() => orderCreateFormTableColumns, []); const userId = useMemo( () => { const authorizationService = getService('authorizationService'); return authorizationService.getUser().user_id; - }, - [] - ); - - const programService = useMemo( - () => { - return getService('programService'); - }, - [] - ); - - const facilityService = useMemo( - () => { - return getService('facilityService'); - }, - [] - ); - - const orderService = useMemo( - () => { - return getService('orderCreateService'); - }, - [] + }, [] ); const supervisoryNodeResource = useMemo( () => { const resource = getService('SupervisoryNodeResource'); return new resource(); - }, - [] + }, [] ); const supplyLineResource = useMemo( () => { const resource = getService('SupplyLineResource'); return new resource(); - }, - [] + }, [] ); + const createOrders = () => { + const orders = getMappedRequestingFacilities(selectedRequestingFacilities, userId, selectedProgram, selectedSupplyingFacility); + goToOrderEdit(orders, orderService, history); + }; + + const updateFilteredFacilities = () => { + const facilities = requestingFacilityOptions + .filter(facility => selectedRequestingFacilities.includes(facility.value)); + + setFilteredRequestingFacilities(facilities); + } + + const updateTableData = (updatedData) => { + setFilteredRequestingFacilities(updatedData); + const updatedDataIds = updatedData.map(facility => facility.value) + + setSelectedRequestingFacilities(prevState => { + return prevState.filter(id => updatedDataIds.includes(id)); + }); + } + + const updateSupplyingFacilities = () => { + setSelectedSupplyingFacility(''); + updateSupplyingFacilitiesValue(selectedProgram, selectedRequestingFacilities, supervisoryNodeResource, supplyLineResource, facilityService, setSupplyingFacilityOptions); + }; + useEffect( () => { programService.getUserPrograms(userId) @@ -101,99 +107,34 @@ const OrderCreateForm = () => { [facilityService] ); - const updateSupplyingFacilities = () => { - selectSupplyingFacility(''); - - if (selectedProgram && selectedRequestingFacility) { - supervisoryNodeResource.query({ - programId: selectedProgram, - facilityId: selectedRequestingFacility - }) - .then((page) => { - const nodes = page.content; - - if (nodes.length > 0) { - Promise.all(nodes.map((node) => ( - supplyLineResource.query({ - programId: selectedProgram, - supervisoryNodeId: node.id - }) - ))) - .then((results) => { - const supplyLines = _.flatten(results.map((it) => (it.content))); - const facilityIds = _.uniq(supplyLines.map((it) => (it.supplyingFacility.id))); - - if (facilityIds.length > 0) { - facilityService.query({ - id: facilityIds - }) - .then((resp) => { - const facilities = resp.content; - setSupplyingFacilityOptions(_.map(facilities, facility => ({ name: facility.name, value: facility.id }))); - }); - } else { - setSupplyingFacilityOptions([]); - } - }); - } else { - setSupplyingFacilityOptions([]); - } - }); - } else { - setSupplyingFacilityOptions([]); - } - }; - useEffect( () => { updateSupplyingFacilities(); }, - [selectedProgram, selectedRequestingFacility] + [selectedProgram, selectedRequestingFacilities] ); useEffect( () => { if (programOptions && programOptions.length === 1) { - selectProgram(programOptions[0].value); + setSelectedProgram(programOptions[0].value); } }, [programOptions] ); - useEffect( - () => { - if (requestingFacilityOptions && requestingFacilityOptions.length === 1) { - selectRequestingFacility(requestingFacilityOptions[0].value); - } - }, - [requestingFacilityOptions] - ); - useEffect( () => { if (supplyingFacilityOptions && supplyingFacilityOptions.length === 1) { - selectSupplyingFacility(supplyingFacilityOptions[0].value); + setSelectedSupplyingFacility(supplyingFacilityOptions[0].value); } }, [supplyingFacilityOptions] ); - const createOrder = () => { - const order = { - emergency: true, - createdBy: { id: userId }, - program: { id: selectedProgram }, - requestingFacility: { id: selectedRequestingFacility }, - receivingFacility: { id: selectedRequestingFacility }, - supplyingFacility: { id: selectedSupplyingFacility }, - facility: { id: selectedRequestingFacility } - }; - - orderService.create(order) - .then((createdOrder) => { - history.push(`/requisitions/orderCreate/${createdOrder.id}`); - }); - }; + useEffect(() => { + updateFilteredFacilities(); + }, [selectedRequestingFacilities]); return (
@@ -206,7 +147,7 @@ const OrderCreateForm = () => { selectProgram(value)} + onChange={value => setSelectedProgram(value)} placeholder="Select program" />
@@ -214,28 +155,35 @@ const OrderCreateForm = () => {
Requesting Facility
selectRequestingFacility(value)} + value={selectedRequestingFacilities.at(-1)} + onChange={value => setSelectedRequestingFacilities(prevState => [...prevState, value])} placeholder="Select requesting facility" /> +
Supplying Facility
selectSupplyingFacility(value)} + onChange={value => setSelectedSupplyingFacility(value)} placeholder="Select supplying facility" - disabled={!selectedProgram || !selectedRequestingFacility} + disabled={!selectedProgram || !selectedRequestingFacilities} />
diff --git a/src/requisition-order-create/order-create-page.jsx b/src/requisition-order-create/order-create-page.jsx index 208725e3..2f159185 100644 --- a/src/requisition-order-create/order-create-page.jsx +++ b/src/requisition-order-create/order-create-page.jsx @@ -13,38 +13,46 @@ * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  */ -import React from 'react'; -import { HashRouter as Router, Route, Switch } from 'react-router-dom'; - +import React, { useMemo } from 'react'; +import { HashRouter as Router, Route, Switch, useLocation } from 'react-router-dom'; import OrderCreateTable from './order-create-table'; import OrderCreateForm from './order-create-form'; import Breadcrumbs from '../react-components/breadcrumbs/breadcrumbs'; +import getService from '../react-components/utils/angular-utils'; -const OrderCreatePage = () => { +const OrderCreateRouting = () => { + const location = useLocation(); + const queryParams = new URLSearchParams(location.search); + const isReadOnly = queryParams.get('isReadOnly') === 'true'; + const { formatMessage } = useMemo(() => getService('messageService'), []); return (
- - - - - - - - - - - + + + + + + + + +
); }; +const OrderCreatePage = () => { + return ( + + + + ); +}; + export default OrderCreatePage; diff --git a/src/requisition-order-create/order-create-requisition-info.jsx b/src/requisition-order-create/order-create-requisition-info.jsx new file mode 100644 index 00000000..12eed557 --- /dev/null +++ b/src/requisition-order-create/order-create-requisition-info.jsx @@ -0,0 +1,36 @@ +import React, { useMemo } from 'react'; +import { formatDate } from '../react-components/utils/format-utils'; +import getService from '../react-components/utils/angular-utils'; + +const OrderCreateRequisitionInfo = ({ order }) => { + const { formatMessage } = useMemo(() => getService('messageService'), []); + + return ( + + ); +}; + +export default OrderCreateRequisitionInfo; diff --git a/src/requisition-order-create/order-create-summary-modal.jsx b/src/requisition-order-create/order-create-summary-modal.jsx new file mode 100644 index 00000000..f9ff725e --- /dev/null +++ b/src/requisition-order-create/order-create-summary-modal.jsx @@ -0,0 +1,67 @@ +import React, { useMemo, useState } from 'react'; +import Modal from '../react-components/modals/modal'; +import EditableTable from '../react-components/table/editable-table'; +import { orderTableColumns } from './order-create.constant'; +import TabNavigation from '../react-components/tab-navigation/tab-navigation'; +import getService from '../react-components/utils/angular-utils'; + +const OrderCreateSummaryModal = ({ isOpen, orders, onSaveClick, onModalClose }) => { + const { formatMessage } = useMemo(() => getService('messageService'), []); + const columns = useMemo(() => orderTableColumns(true, formatMessage), []); + const [currentTab, setCurrentTab] = useState(0); + + return ( + +
+ Orders Summary +
+
+ ({ + header: order.facility.name, + key: order.id, + isActive: currentTab === index + })), + onTabChange: (index) => { + setCurrentTab(index); + } + } + } + > +
+
+ { + currentTab !== undefined && + + } +
+
+
+
+ + +
+ } + > +
+ ); +}; + +export default OrderCreateSummaryModal; diff --git a/src/requisition-order-create/order-create-tab.jsx b/src/requisition-order-create/order-create-tab.jsx new file mode 100644 index 00000000..0a742a73 --- /dev/null +++ b/src/requisition-order-create/order-create-tab.jsx @@ -0,0 +1,141 @@ +import React, { useState, useMemo, useEffect } from 'react'; +import Tippy from '@tippyjs/react'; + +import EditableTable from '../react-components/table/editable-table'; +import getService from '../react-components/utils/angular-utils'; +import OrderCreateRequisitionInfo from './order-create-requisition-info'; + +import { SearchSelect } from './search-select'; +import { getUpdatedOrder } from './order-create-table-helper-functions'; +import { orderTableColumns } from './order-create.constant'; +import { validateOrderItem } from './order-create-validation-helper-functions'; + +const OrderCreateTab = ({ passedOrder, + updateOrderArray, + showValidationErrors, + isTableReadOnly, + stockCardSummaryRepositoryImpl, + tabIndex, + cacheOrderableOptions, + cachedOrderableOptions }) => { + const [order, setOrder] = useState({ orderLineItems: [], ...passedOrder }); + const [selectedOrderable, setSelectedOrderable] = useState(''); + const [orderableOptions, setOrderableOptions] = useState(cachedOrderableOptions); + const columns = useMemo(() => orderTableColumns(isTableReadOnly), []); + const orderCreatePrintService = useMemo(() => getService('orderCreatePrintService'), []); + + useMemo(() => { + if (cachedOrderableOptions?.length) { + setOrderableOptions(cachedOrderableOptions); + return; + } + + stockCardSummaryRepositoryImpl.query({ + programId: order.program.id, + facilityId: order.requestingFacility.id + }).then((page) => { + const fetchedOrderableOptions = page.content + const orderableOptionsValue = fetchedOrderableOptions.map(stockItem => ({ + name: stockItem.orderable.fullProductName, + value: { ...stockItem.orderable, soh: stockItem.stockOnHand } + })); + + setOrderableOptions(orderableOptionsValue); + }); + }, []); + + useEffect(() => { + if (orderableOptions.length > 0) { + cacheOrderableOptions(orderableOptions, tabIndex); + } + }, [orderableOptions]); + + const updateData = (changedItems) => { + const updatedOrder = { + ...order, + orderLineItems: changedItems + }; + + setOrder(updatedOrder); + updateOrderArray(updatedOrder); + }; + + const addOrderable = () => { + const updatedOrder = getUpdatedOrder(selectedOrderable, order); + setOrder(updatedOrder); + setSelectedOrderable(''); + updateOrderArray(updatedOrder); + }; + + const validateRow = (row) => { + const errors = validateOrderItem(row); + return !errors.length; + }; + + const printOrder = () => { + orderCreatePrintService.print(order.id); + }; + + const isProductAdded = selectedOrderable && _.find(order.orderLineItems, item => (item.orderable.id === selectedOrderable.id)); + + return ( + <> + +
+
+
+
+ { + !isTableReadOnly && + setSelectedOrderable(value)} + objectKey={'id'} + disabled={isTableReadOnly} + >Product + } +
+ + { + !isTableReadOnly && +
+ +
+ } +
+ { + isTableReadOnly && + + } +
+
+ +
+
+
+ + + ); +}; + +export default OrderCreateTab; diff --git a/src/requisition-order-create/order-create-table-helper-functions.jsx b/src/requisition-order-create/order-create-table-helper-functions.jsx new file mode 100644 index 00000000..e5bdd2e9 --- /dev/null +++ b/src/requisition-order-create/order-create-table-helper-functions.jsx @@ -0,0 +1,78 @@ +export const getOrderValue = (fetchedOrder, stockCardSummaryRepositoryImpl) => { + const orderableIds = fetchedOrder.orderLineItems.map((lineItem) => lineItem.orderable.id); + + if (orderableIds?.length) { + return stockCardSummaryRepositoryImpl.query({ + programId: fetchedOrder.program.id, + facilityId: fetchedOrder.requestingFacility.id, + orderableId: orderableIds + }).then(function (page) { + return getOrdersWithSoh(page, fetchedOrder); + }).catch(function () { + return fetchedOrder; + }); + } else { + return Promise.resolve(fetchedOrder); + } +}; + +const getOrdersWithSoh = (page, fetchedOrder) => { + const stockItems = page.content; + const orderWithSoh = { + ...fetchedOrder, + orderLineItems: fetchedOrder.orderLineItems.map((lineItem) => { + const stockItem = stockItems.find((item) => (item.orderable.id === lineItem.orderable.id)); + + return { + ...lineItem, + soh: stockItem ? stockItem.stockOnHand : 0 + }; + }) + }; + + return orderWithSoh; +}; + +export const getUpdatedOrder = (selectedOrderable, order) => { + const newLineItem = { + orderedQuantity: '', + soh: selectedOrderable.soh, + orderable: { + id: selectedOrderable.id, + productCode: selectedOrderable.productCode, + fullProductName: selectedOrderable.fullProductName, + meta: { + versionNumber: selectedOrderable.meta.versionNumber + } + } + }; + + const orderNewLineItems = [...order.orderLineItems, newLineItem]; + + const updatedOrder = { + ...order, + orderLineItems: orderNewLineItems + }; + + return updatedOrder; +}; + +export const getIsOrderValidArray = (orders) => { + return orders.map((order) => { + if (!order?.orderLineItems.length) { + return false; + } + const mappedOrderLineItems = order.orderLineItems.map(item => item.orderedQuantity !== '' && item.orderedQuantity > 0); + return Boolean(!mappedOrderLineItems.includes(false) && order.id !== undefined) + }); +}; + +export const createOrderDisabled = (orders) => { + const mappedOrders = getIsOrderValidArray(orders); + return mappedOrders.length === 0 || mappedOrders.includes(false); +} + +export const saveDraftDisabled = (orders) => { + const mappedOrders = orders.map((order) => order.id !== undefined); + return mappedOrders.length === 0 || mappedOrders.includes(false); +} diff --git a/src/requisition-order-create/order-create-table.jsx b/src/requisition-order-create/order-create-table.jsx index 4e0e264b..c4d8ef97 100644 --- a/src/requisition-order-create/order-create-table.jsx +++ b/src/requisition-order-create/order-create-table.jsx @@ -14,41 +14,32 @@ */ import React, { useState, useEffect, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; import { useParams, useHistory } from 'react-router-dom'; import { toast } from 'react-toastify'; -import { useDispatch } from 'react-redux'; -import Tippy from '@tippyjs/react'; - -import TrashButton from '../react-components/buttons/trash-button'; -import EditableTable from '../react-components/table/editable-table'; -import InputCell from '../react-components/table/input-cell'; - -import { formatDate } from '../react-components/utils/format-utils'; import getService from '../react-components/utils/angular-utils'; -import { SearchSelect } from './search-select'; +import { createOrderDisabled, getIsOrderValidArray, getOrderValue, saveDraftDisabled } from './order-create-table-helper-functions'; +import OrderCreateTab from './order-create-tab'; import { saveDraft, createOrder } from './reducers/orders.reducer'; +import { isOrderInvalid } from './order-create-validation-helper-functions'; +import OrderCreateSummaryModal from './order-create-summary-modal'; +import TabNavigation from '../react-components/tab-navigation/tab-navigation'; -const OrderCreateTable = () => { +const OrderCreateTable = ({ isReadOnly }) => { + const [orders, setOrders] = useState([]); + const [currentTab, setCurrentTab] = useState(0); + const [showValidationErrors, setShowValidationErrors] = useState(false); + const [isSummaryModalOpen, setIsSummaryModalOpen] = useState(false); + const [cachedOrderableOptions, setCachedOrderableOptions] = useState([]); - const history = useHistory(); - const { orderId } = useParams(); + const { orderIds } = useParams(); const dispatch = useDispatch(); + const history = useHistory(); - const [order, setOrder] = useState({ orderLineItems: [] }); - const [orderParams, setOrderParams] = useState({ programId: null , requestingFacilityId: null }); - - const [orderableOptions, setOrderableOptions] = useState([]); - const [selectedOrderable, selectOrderable] = useState(''); - - const [showValidationErrors, setShowValidationErrors] = useState(false); - - const orderService = useMemo( - () => { - return getService('orderCreateService'); - }, - [] - ); + const orderService = useMemo(() => getService('orderCreateService'), []); + const notificationService = useMemo(() => getService('notificationService'), []); + const offlineService = useMemo(() => getService('offlineService'), []); const stockCardSummaryRepositoryImpl = useMemo( () => { @@ -58,319 +49,148 @@ const OrderCreateTable = () => { [] ); - const notificationService = useMemo( - () => { - return getService('notificationService'); - }, - [] - ); - - const offlineService = useMemo( - () => { - return getService('offlineService'); - }, - [] - ); - useEffect( () => { - orderService.get(orderId) - .then((fetchedOrder) => { - const orderParams = { - programId: fetchedOrder.program.id, - facilityId: fetchedOrder.requestingFacility.id - }; - - setOrderParams(orderParams); - - const orderableIds = fetchedOrder.orderLineItems.map((lineItem) => { - return lineItem.orderable.id; - }); - - if (orderableIds && orderableIds.length) { - stockCardSummaryRepositoryImpl.query({ - programId: fetchedOrder.program.id, - facilityId: fetchedOrder.requestingFacility.id, - orderableId: orderableIds - }) - .then(function(page) { - const stockItems = page.content; - - const orderWithSoh = { - ...fetchedOrder, - orderLineItems: fetchedOrder.orderLineItems.map((lineItem) => { - const stockItem =_.find(stockItems, (item) => (item.orderable.id === lineItem.orderable.id)); - - return { - ...lineItem, - soh: stockItem ? stockItem.stockOnHand : 0 - }; - }) - }; - - setOrder(orderWithSoh); - }) - .catch(function() { - setOrder(fetchedOrder); + if (orderIds) { + const orderIdsArray = orderIds.split(','); + const ordersPromises = orderIdsArray.map(orderId => orderService.get(orderId)); + setCachedOrderableOptions(orderIdsArray.map(() => [])); + Promise.all(ordersPromises) + .then((orders) => { + const orderValuePromisses = orders.map(order => getOrderValue(order, stockCardSummaryRepositoryImpl)); + Promise.all(orderValuePromisses) + .then((orders) => { + setOrders(orders); }); - } else { - setOrder(fetchedOrder); - } - }); - }, - [orderService] - ); - - useEffect( - () => { - if (orderParams.programId !== null && orderParams.requestingFacilityId !== null) { - stockCardSummaryRepositoryImpl.query({ - programId: orderParams.programId, - facilityId: orderParams.facilityId - }) - .then(function(page) { - const stockItems = page.content; - - setOrderableOptions(_.map(stockItems, stockItem => ({ - name: stockItem.orderable.fullProductName, - value: { ...stockItem.orderable, soh: stockItem.stockOnHand } - }))); }); } }, - [orderParams] - ); - - const columns = useMemo( - () => [ - { - Header: 'Product Code', - accessor: 'orderable.productCode' - }, - { - Header: 'Product', - accessor: 'orderable.fullProductName' - }, - { - Header: 'SOH', - accessor: 'soh', - Cell: ({ value }) => (
{value}
) - }, - { - Header: 'Quantity', - accessor: 'orderedQuantity', - Cell: (props) => ( - - ) - }, - { - Header: 'Actions', - accessor: 'id', - Cell: ({ row: { index }, deleteRow }) => ( - deleteRow(index)} /> - ) - } - ], - [] + [orderService] ); - const validateOrderItem = (item) => { - const errors = []; - - if (item.orderedQuantity === null || item.orderedQuantity === undefined - || item.orderedQuantity === '') { - errors.push('Order quantity is required'); - } else if (item.orderedQuantity < 0) { - errors.push('Order quantity cannot be negative'); - } - - return errors; - }; - - const validateRow = (row) => { - const errors = validateOrderItem(row); - - return !errors.length; - }; - - const validateOrder = (orderToValidate) => { - const lineItems = orderToValidate.orderLineItems; - let errors = []; - - if (!lineItems) { - return errors; - } - - lineItems.forEach(item => { - errors = errors.concat(validateOrderItem(item)); + const onProductAdded = (updatedOrder) => { + setOrders(prevOrders => { + const updatedOrders = prevOrders.map(order => { + if (order.id === updatedOrder.id) { + return updatedOrder; + } + return order; + }); + return updatedOrders; }); + } - return _.uniq(errors); - }; - - const updateData = (changedItems) => { - const updatedOrder = { - ...order, - orderLineItems: changedItems - }; - - setOrder(updatedOrder); - }; - - const updateOrder = () => { - const validationErrors = validateOrder(order); - - if (validationErrors.length) { - validationErrors.forEach(error => { - toast.error(error); - }); - setShowValidationErrors(true); + const sendOrders = () => { + if (isOrderInvalid(orders, setShowValidationErrors, toast)) { return; } if (offlineService.isOffline()) { - dispatch(saveDraft(order)); - toast.success("Draft order saved offline"); + dispatch(createOrder(orders[currentTab])); + notificationService.success("Offline order created successfully. It will be sent when you are online."); + history.push('/'); } else { - setShowValidationErrors(false); - - orderService.update(order) - .then(() => { - toast.success("Order saved successfully"); - }); + const orderCreatePromisses = orders.map(order => orderService.send(order)); + Promise.all(orderCreatePromisses).then(() => { + notificationService.success('requisition.orderCreate.submitted'); + history.push('/orders/fulfillment'); + }); } }; - const addOrderable = () => { - const newLineItem = { - orderedQuantity: '', - soh: selectedOrderable.soh, - orderable: { - id: selectedOrderable.id, - productCode: selectedOrderable.productCode, - fullProductName: selectedOrderable.fullProductName, - meta: { - versionNumber: selectedOrderable.meta.versionNumber - } - } - }; - - let orderNewLineItems = [...order.orderLineItems, newLineItem]; - - const updatedOrder = { - ...order, - orderLineItems: orderNewLineItems - }; - - setOrder(updatedOrder); - selectOrderable(''); - }; - - const sendOrder = () => { - const validationErrors = validateOrder(order); - - if (validationErrors.length) { - validationErrors.forEach(error => { - toast.error(error); - }); - setShowValidationErrors(true); + const updateOrders = () => { + if (isOrderInvalid(orders, setShowValidationErrors, toast)) { return; } if (offlineService.isOffline()) { - dispatch(createOrder(order)); - notificationService.success("Offline order created successfully. It will be sent when you are online."); - history.push('/'); + dispatch(saveDraft(orders[currentTab])); + toast.success("Draft order saved offline"); } else { - orderService.send(order) - .then(() => { - notificationService.success('requisition.orderCreate.submitted'); - history.push('/orders/fulfillment'); - }); + setShowValidationErrors(false); + const updateOrdersPromises = orders.map(order => orderService.update(order)); + Promise.all(updateOrdersPromises).then(() => { + toast.success("Orders saved successfully"); + }); } }; - const isProductAdded = selectedOrderable && _.find(order.orderLineItems, item => (item.orderable.id === selectedOrderable.id)); - return (
+ { + isSummaryModalOpen && + setIsSummaryModalOpen(false)} + /> + }

Create Order

-
- -
-
-
- selectOrderable(value)} - objectKey={'id'} - >Product - -
- -
-
-
- -
+ { + orders.length > 0 && +
+ ({ + header: order.facility.name, + key: order.id, + isActive: currentTab === index + })), + onTabChange: (index) => { + setCurrentTab(index); + }, + isTabValidArray: !isReadOnly ? getIsOrderValidArray(orders) : undefined + } + } + >
+ } +
+ {(orders.length > 0) ? ( + { + const updatedCachedOrderableOptions = cachedOrderableOptions; + updatedCachedOrderableOptions[tabIndex] = orderableOptions; + setCachedOrderableOptions(updatedCachedOrderableOptions); + }} + updateOrderArray={ + (updatedOrder) => { + onProductAdded(updatedOrder); + } + } /> + ) : ( +

Loading...

+ )}
- - + { + isReadOnly || + <> + + + + }
); diff --git a/src/requisition-order-create/order-create-validation-helper-functions.jsx b/src/requisition-order-create/order-create-validation-helper-functions.jsx new file mode 100644 index 00000000..1ba44ac5 --- /dev/null +++ b/src/requisition-order-create/order-create-validation-helper-functions.jsx @@ -0,0 +1,45 @@ +export const validateOrderItem = (item) => { + const errors = []; + + if (item.orderedQuantity === null || item.orderedQuantity === undefined + || item.orderedQuantity === '') { + errors.push('Order quantity is required'); + } else if (item.orderedQuantity < 0) { + errors.push('Order quantity cannot be negative'); + } + + return errors; +}; + +const validateOrder = (orderToValidate) => { + const lineItems = orderToValidate.orderLineItems; + let errors = []; + + if (!lineItems) { + return errors; + } + + lineItems.forEach(item => { + errors = errors.concat(validateOrderItem(item)); + }); + + return _.uniq(errors); +}; + +export const isOrderInvalid = (orders, setShowValidationErrors, toast) => { + let validationErrors = []; + + orders.forEach(order => { + validationErrors = validationErrors.concat(validateOrder(order)); + }); + + if (validationErrors.length) { + validationErrors.forEach(error => { + toast.error(error); + }); + setShowValidationErrors(true); + return true; + } + + return false; +} diff --git a/src/requisition-order-create/order-create.constant.jsx b/src/requisition-order-create/order-create.constant.jsx new file mode 100644 index 00000000..e9d1b4a5 --- /dev/null +++ b/src/requisition-order-create/order-create.constant.jsx @@ -0,0 +1,81 @@ +import React from 'react'; +import TrashButton from '../react-components/buttons/trash-button'; +import InputCell from '../react-components/table/input-cell'; + +const orderTableDefaultColumns = (formatMessage) => [ + { + Header: formatMessage('requisition.orderCreate.table.productCode'), + accessor: 'orderable.productCode' + }, + { + Header: formatMessage('requisition.orderCreate.table.product'), + accessor: 'orderable.fullProductName' + }, + { + Header: formatMessage('requisition.orderCreate.table.soh'), + accessor: 'soh', + Cell: ({ value }) => (
{value}
) + }, + { + Header: formatMessage('requisition.orderCreate.table.quantity'), + accessor: 'orderedQuantity', + Cell: (props) => ( + + ) + }, + { + Header: formatMessage('requisition.orderCreate.table.actions'), + accessor: 'id', + Cell: ({ row: { index }, deleteRow }) => ( + deleteRow(index)} /> + ) + } +]; + +const orderReadonlyTableColumns = (formatMessage) => [ + { + Header: formatMessage('requisition.orderCreate.table.productCode'), + accessor: 'orderable.productCode' + }, + { + Header: formatMessage('requisition.orderCreate.table.product'), + accessor: 'orderable.fullProductName' + }, + { + Header: formatMessage('requisition.orderCreate.table.soh'), + accessor: 'soh', + Cell: ({ value }) => (
{value}
) + }, + { + Header: formatMessage('requisition.orderCreate.table.quantity'), + accessor: 'orderedQuantity', + } +]; + +export const orderTableColumns = (isTableReadOnly, formatMessage) => { + return isTableReadOnly ? orderReadonlyTableColumns(formatMessage) : + orderTableDefaultColumns(formatMessage); +} + +export const orderCreateFormTableColumns = (formatMessage) => [ + { + Header: formatMessage('requisition.orderCreate.table.facility'), + accessor: 'name' + }, + { + Header: formatMessage('requisition.orderCreate.table.actions'), + accessor: 'value', + Cell: ({ row: { index }, deleteRow }) => ( + deleteRow(index)} /> + ) + } +]; + +export const ORDER_STATUS = { + CREATING: 'CREATING', +}; diff --git a/src/requisition-order-create/order-create.scss b/src/requisition-order-create/order-create.scss index 6b6df6f6..7f1052f8 100644 --- a/src/requisition-order-create/order-create.scss +++ b/src/requisition-order-create/order-create.scss @@ -2,6 +2,20 @@ $accent-color: #49baeb; $light-grey: #bebebe; + .tabs-container { + width: calc(100% - 2em); + margin-left: 1em; + } + + .requisition-info { + width: calc(100% - 2em); + margin: 1em 0 0 1em; + position: relative; + background-color: $background-color-alt; + border: 1px solid $border-color; + border-radius: $border-radius; + } + .order-create-form { .section { margin: 0.5em 0; @@ -19,6 +33,10 @@ min-width: 400px; } } + + .facilities-table { + width: fit-content; + } } .order-create-table-container { @@ -34,13 +52,25 @@ justify-content: space-between; width: 100%; margin-bottom: 1em; + + .buttons-container { + @include flex-layout(row, $gap: 1rem); + + .order-print { + @include icon('print'); + + &::before { + margin-right: .5em; + } + } + } } } } .page-footer { button { - @include margin-right(1em); + margin-right: 1em; } justify-content: flex-end; @@ -52,16 +82,15 @@ max-height: 40vh; .search { - .input-wrapper { - display:inline-block; + display: inline-block; position: relative; &:before { - font-family: 'FONTAWESOME'; - content: '\f0d7'; // fa-caret-down + font-family: "FONTAWESOME"; + content: "\f0d7"; // fa-caret-down position: absolute; - @include left(1em); + left: 1em; color: $light-grey; top: 50%; transform: translateY(-50%); @@ -72,7 +101,7 @@ border: 1px solid $light-grey; background-color: inherit; border-radius: 4px; - @include padding(0.5em, 0.5em, 0.5em, 2em); + padding: 0.5em 0.5em 0.5em 2em; &:focus { outline: none; @@ -82,7 +111,7 @@ } .clear-icon { - @include margin-left(-2em); + margin-left: -2em; color: $light-grey; &:hover { @@ -101,7 +130,7 @@ .select { position: absolute; - z-index:100; + z-index: 100; border: $light-grey 1px solid; box-shadow: 0 0.25em 1em #e4e4e4; background-color: #fff; @@ -136,12 +165,12 @@ font: inherit; outline: inherit; padding: 0.5em; - @include text-align(left); + text-align: left; width: 100%; &.is-selected { - background-color: $accent-color; - color: #fff; + background-color: $accent-color; + color: #fff; } } } diff --git a/src/select-products-modal/messages_en.json b/src/select-products-modal/messages_en.json new file mode 100644 index 00000000..2bd409fb --- /dev/null +++ b/src/select-products-modal/messages_en.json @@ -0,0 +1,14 @@ +{ + "selectProductsModal.addProducts": "Add Products", + "selectProductsModal.searchProduct": "Search Product", + "selectProductsModal.search": "Search", + "selectProductsModal.select": "Select", + "selectProductsModal.code": "Code", + "selectProductsModal.product": "Product", + "selectProductsModal.uoi": "Unit of Issue", + "selectProductsModal.cancel": "Cancel", + "selectProductsModal.addProducts.emptyList": "You have not selected any products.", + "selectProductsModal.productCode": "Product Code", + "selectProductsModal.productName": "Product Name", + "selectProductsModal.searchByCodeInfo": "Please type in a part of the product name or the first few letters of the product code." +} \ No newline at end of file diff --git a/src/select-products-modal/select-products-modal-state.provider.js b/src/select-products-modal/select-products-modal-state.provider.js new file mode 100644 index 00000000..4f07d703 --- /dev/null +++ b/src/select-products-modal/select-products-modal-state.provider.js @@ -0,0 +1,124 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +(function() { + + 'use strict'; + + /** + * @ngdoc service + * @name select-products-modal.selectProductsModalState + * + * @description + * Provider for defining states which should be displayed as modals. + */ + angular + .module('select-products-modal') + .provider('selectProductsModalState', selectProductsModalStateProvider); + + selectProductsModalStateProvider.$inject = ['modalStateProvider', '$stateProvider']; + + function selectProductsModalStateProvider(modalStateProvider, $stateProvider) { + this.stateWithAddOrderablesChildState = stateWithAddOrderablesChildState; + this.$get = [function() {}]; + + /** + * @ngdoc method + * @methodOf select-products-modal.selectProductsModalState + * @name state + * + * @description + * Defines a state which should be displayed as modal. Currently the resolves from parent + * states are not available in the controller by default. To make them available please + * include them in the parentResolves parameter line this + * + * ``` + * selectProductsModalStateProvider.state('some.state', { + * parentResolves: ['someParentResolve'] + * }); + * ``` + * + * @param {String} stateName the name of the state + * @param {Object} state the state definition + */ + function stateWithAddOrderablesChildState(stateName, state) { + + if (state.display === 'modal') { + modalStateProvider.state(stateName, state); + } else { + $stateProvider.state(stateName, state); + } + + modalStateProvider + .state(stateName + '.addOrderables', { + controller: 'SelectProductsModalController', + controllerAs: 'vm', + templateUrl: 'select-products-modal/select-products-modal.html', + label: 'adminOrderableEdit.kitUnpackList', + nonTrackable: true, + params: { + addOrderablesPage: undefined, + addOrderablesSize: undefined, + productName: undefined, + productCode: undefined, + search: undefined + }, + resolve: { + external: function(selectProductsModalService) { + return !selectProductsModalService.getOrderables(); + }, + isUnpackKitState: function() { + return stateName === 'openlmis.administration.orderables.edit.kitUnpackList.edit'; + }, + orderables: function(OrderableResource, paginationService, $stateParams, + selectProductsModalService, isUnpackKitState) { + var orderables = selectProductsModalService.getOrderables(); + + if (orderables) { + return paginationService.registerList(undefined, $stateParams, function() { + return orderables; + }, { + paginationId: 'addOrderables' + }); + } + return paginationService.registerUrl($stateParams, function(stateParams) { + var params; + if (isUnpackKitState) { + params = { + sort: 'fullProductName,asc', + page: stateParams.page, + size: stateParams.size, + code: stateParams.productCode, + name: stateParams.productName + }; + } else { + params = { + sort: 'fullProductName,asc', + page: stateParams.page, + size: stateParams.size, + search: stateParams.search + }; + } + return new OrderableResource().query(params); + }, { + paginationId: 'addOrderables' + }); + } + } + }); + } + } + +})(); diff --git a/src/select-products-modal/select-products-modal-state.provider.spec.js b/src/select-products-modal/select-products-modal-state.provider.spec.js new file mode 100644 index 00000000..728d27b0 --- /dev/null +++ b/src/select-products-modal/select-products-modal-state.provider.spec.js @@ -0,0 +1,223 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +describe('selectProductsModalStateProvider', function() { + + beforeEach(function() { + var context = this; + module('openlmis-modal-state', function(modalStateProvider, $stateProvider) { + context.modalStateProvider = modalStateProvider; + context.$stateProvider = $stateProvider; + }); + + module('select-products-modal', function(selectProductsModalStateProvider) { + context.selectProductsModalStateProvider = selectProductsModalStateProvider; + }); + + module('openlmis-pagination'); + + inject(function($injector) { + this.selectProductsModalService = $injector.get('selectProductsModalService'); + this.OrderableResource = $injector.get('OrderableResource'); + this.OrderableDataBuilder = $injector.get('OrderableDataBuilder'); + this.PageDataBuilder = $injector.get('PageDataBuilder'); + this.$q = $injector.get('$q'); + this.$rootScope = $injector.get('$rootScope'); + this.$state = $injector.get('$state'); + this.$controller = $injector.get('$controller'); + this.paginationService = $injector.get('paginationService'); + }); + + this.orderables = [ + new this.OrderableDataBuilder().build(), + new this.OrderableDataBuilder().build() + ]; + + this.orderablesPage = new this.PageDataBuilder() + .withContent(this.orderables) + .build(); + + this.$scope = this.$rootScope.$new(); + + this.paginationController = this.$controller('PaginationController', { + $scope: this.$scope, + paginationService: this.paginationService + }); + + spyOn(this.OrderableResource.prototype, 'query').andReturn(this.$q.resolve(this.orderablesPage)); + spyOn(this.selectProductsModalService, 'getOrderables').andReturn(); + spyOn(this.selectProductsModalService, 'getSelections').andReturn({}); + + spyOn(this.modalStateProvider, 'state').andCallFake(function(stateName, config) { + context.config = config; + context.stateName = stateName; + return context; + }); + + this.goToState = function() { + this.$state.go.apply(this, arguments); + this.$rootScope.$apply(); + }; + + this.getResolvedValue = function(name) { + return this.$state.$current.locals.globals[name]; + }; + }); + + describe('stateWithAddOrderablesChildState', function() { + + beforeEach(function() { + spyOn(this.$stateProvider, 'state').andReturn(); + }); + + it('should register the state as page if no display is defined', function() { + this.selectProductsModalStateProvider.stateWithAddOrderablesChildState('main', { + url: '/main' + }); + + expect(this.$stateProvider.state).toHaveBeenCalledWith('main', { + url: '/main' + }); + }); + + it('should register the state as modal if display is set to modal', function() { + this.selectProductsModalStateProvider.stateWithAddOrderablesChildState('mainModal', { + url: '/mainModal', + display: 'modal' + }); + + expect(this.modalStateProvider.state).toHaveBeenCalledWith('mainModal', { + url: '/mainModal', + display: 'modal' + }); + }); + + }); + + describe('.addOrderables', function() { + + beforeEach(function() { + this.selectProductsModalStateProvider.stateWithAddOrderablesChildState('main', { + url: '/main' + }); + + }); + + it('should set controller', function() { + expect(this.config.controller).toBe('SelectProductsModalController'); + }); + + it('should set controllerAs', function() { + expect(this.config.controllerAs).toBe('vm'); + }); + + it('should set templateUrl', function() { + expect(this.config.templateUrl).toBe('select-products-modal/select-products-modal.html'); + }); + + it('should set label', function() { + expect(this.config.label).toBe('adminOrderableEdit.kitUnpackList'); + }); + + it('should set nonTrackable', function() { + expect(this.config.nonTrackable).toBeTruthy(); + }); + + it('should set params', function() { + expect(this.config.params.addOrderablesPage).toBeUndefined(); + expect(this.config.params.addOrderablesSize).toBeUndefined(); + expect(this.config.params.productName).toBeUndefined(); + expect(this.config.params.productCode).toBeUndefined(); + }); + + it('should resolve external as false if no orderables provided', function() { + this.selectProductsModalService.getOrderables.andReturn(this.orderables); + + var result = this.config.resolve.external(this.selectProductsModalService); + + expect(result).toBeFalsy(); + expect(this.selectProductsModalService.getOrderables).toHaveBeenCalled(); + }); + + it('should resolve external as true if any orderable provided', function() { + this.selectProductsModalService.getOrderables.andReturn(); + + var result = this.config.resolve.external(this.selectProductsModalService); + + expect(result).toBeTruthy(); + expect(this.selectProductsModalService.getOrderables).toHaveBeenCalled(); + }); + + it('should resolve orderables for external pagination', function() { + this.selectProductsModalService.getOrderables.andReturn(this.orderables); + + this.config.resolve.orderables(this.OrderableResource, this.paginationService, + this.$state.params, this.selectProductsModalService); + + expect(this.selectProductsModalService.getOrderables).toHaveBeenCalled(); + }); + + it('should resolve matching orderables on unpack kit screen', function() { + this.nameParam = 'Product'; + this.codeParam = 'C100'; + this.$state.params = { + page: 0, + size: 10, + productName: this.nameParam, + productCode: this.codeParam + }; + + this.selectProductsModalService.getOrderables.andReturn(); + + this.config.resolve.orderables(this.OrderableResource, this.paginationService, + this.$state.params, this.selectProductsModalService, true); + + expect(this.selectProductsModalService.getOrderables).toHaveBeenCalled(); + + expect(this.OrderableResource.prototype.query).toHaveBeenCalledWith({ + sort: 'fullProductName,asc', + page: 0, + size: 10, + name: this.nameParam, + code: this.codeParam + }); + }); + + it('should resolve matching orderables on any screen except unpack kit', function() { + this.search = 'search text'; + this.$state.params = { + page: 0, + size: 10, + search: this.search + }; + + this.selectProductsModalService.getOrderables.andReturn(); + + this.config.resolve.orderables(this.OrderableResource, this.paginationService, + this.$state.params, this.selectProductsModalService, false); + + expect(this.selectProductsModalService.getOrderables).toHaveBeenCalled(); + + expect(this.OrderableResource.prototype.query).toHaveBeenCalledWith({ + sort: 'fullProductName,asc', + page: 0, + size: 10, + search: this.search + }); + }); + + }); + +}); diff --git a/src/select-products-modal/select-products-modal.controller.js b/src/select-products-modal/select-products-modal.controller.js new file mode 100644 index 00000000..5a86813b --- /dev/null +++ b/src/select-products-modal/select-products-modal.controller.js @@ -0,0 +1,114 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +(function() { + + 'use strict'; + + /** + * @ngdoc controller + * @name select-products-modal.controller:SelectProductsModalController + * + * @description + * Manages Select Products Modal. + */ + angular + .module('select-products-modal') + .controller('SelectProductsModalController', controller); + + controller.$inject = ['orderables', '$state', 'selectProductsModalService', 'external', '$stateParams', + 'isUnpackKitState']; + + function controller(orderables, $state, selectProductsModalService, external, $stateParams, + isUnpackKitState) { + var vm = this; + + vm.$onInit = onInit; + vm.selectProducts = selectProductsModalService.resolve; + vm.close = selectProductsModalService.reject; + vm.search = search; + + /** + * @ngdoc method + * @methodOf select-products-modal.controller:SelectProductsModalController + * @name $onInit + * + * @description + * Initialization method of the SelectProductsModalController. + */ + function onInit() { + vm.orderables = orderables; + vm.selections = selectProductsModalService.getSelections(); + vm.selected = _.mapObject(vm.selections, function(selection) { + return !!selection; + }); + vm.external = external; + vm.code = $stateParams.productCode; + vm.name = $stateParams.productName; + vm.searchText = $stateParams.search; + vm.filteredOrderables = filterOrderables(orderables, $stateParams.search); + vm.isUnpackKitState = isUnpackKitState; + } + + /** + * @ngdoc method + * @methodOf select-products-modal.controller:SelectProductsModalController + * @name search + * + * @description + * Refreshes the product list so the add product dialog box shows only relevant products + * without reloading parent state. + */ + function search() { + if (vm.isUnpackKitState) { + var stateParams = angular.copy($stateParams); + stateParams.productCode = vm.code; + stateParams.productName = vm.name; + $state.go('.', stateParams, { + reload: $state.$current.name, + notify: false + }); + } else { + $state.go('.', _.extend({}, $stateParams, { + search: vm.searchText + })); + } + } + + function filterOrderables(orderables, searchText) { + if (searchText) { + return orderables.filter(searchByCodeAndName); + } + return orderables; + } + + function searchByCodeAndName(orderable) { + var searchText = vm.searchText.toLowerCase(); + var foundInFullProductName; + var foundInProductCode; + + if (orderable.productCode !== undefined) { + foundInProductCode = orderable.productCode.toLowerCase().startsWith(searchText); + } + + if (orderable.fullProductName !== undefined) { + foundInFullProductName = orderable.fullProductName.toLowerCase().contains(searchText); + } + return foundInFullProductName || foundInProductCode; + } + + } + +})(); diff --git a/src/select-products-modal/select-products-modal.controller.spec.js b/src/select-products-modal/select-products-modal.controller.spec.js new file mode 100644 index 00000000..1cde4bb0 --- /dev/null +++ b/src/select-products-modal/select-products-modal.controller.spec.js @@ -0,0 +1,278 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +describe('SelectProductsModalController', function() { + + beforeEach(function() { + module('select-products-modal'); + + inject(function($injector) { + this.$q = $injector.get('$q'); + this.$rootScope = $injector.get('$rootScope'); + this.$controller = $injector.get('$controller'); + this.alertService = $injector.get('alertService'); + this.OrderableDataBuilder = $injector.get('OrderableDataBuilder'); + this.selectProductsModalService = $injector.get('selectProductsModalService'); + this.$state = $injector.get('$state'); + }); + + this.external = false; + + this.orderables = [ + new this.OrderableDataBuilder() + .withFullProductName('Product One') + .withProductCode('PC1') + .build(), + new this.OrderableDataBuilder() + .withFullProductName('Product Two pc2') + .withProductCode('PS1') + .build(), + new this.OrderableDataBuilder() + .withFullProductName('Product Three') + .withProductCode('XB1') + .build(), + new this.OrderableDataBuilder() + .withFullProductName('Product Four') + .withProductCode('N64') + .build(), + new this.OrderableDataBuilder() + .withFullProductName('undefined') + .withProductCode('undefined') + .build(), + new this.OrderableDataBuilder() + .withFullProductName('Counter Something') + .withProductCode('Co1') + .build(), + new this.OrderableDataBuilder() + .withFullProductName('Another product') + .withProductCode('Code Name') + .build(), + new this.OrderableDataBuilder() + .withFullProductName('Some Product') + .withProductCode('CD1') + .build(), + new this.OrderableDataBuilder() + .withFullProductName('Same Product to displayed') + .withoutProductCode() + .build() + ]; + + this.selections = {}; + this.selections[this.orderables[0].id] = this.orderables[0]; + this.$stateParams = {}; + + spyOn(this.$state, 'go'); + spyOn(this.selectProductsModalService, 'getSelections').andReturn(this.selections); + + this.initController = function() { + this.vm = this.$controller('SelectProductsModalController', { + modalDeferred: this.modalDeferred, + orderables: this.orderables, + external: this.external, + $stateParams: this.$stateParams, + isUnpackKitState: false + }); + this.vm.$onInit(); + }; + }); + + describe('$onInit', function() { + + it('should expose orderables', function() { + this.initController(); + + expect(this.vm.orderables).toEqual(this.orderables); + }); + + it('should expose code', function() { + this.$stateParams.productCode = 'C100'; + this.initController(); + + expect(this.vm.code).toEqual(this.$stateParams.productCode); + }); + + it('should expose name', function() { + this.$stateParams.productName = 'Product'; + this.initController(); + + expect(this.vm.name).toEqual(this.$stateParams.productName); + }); + + it('should expose filteredOrderables', function() { + this.initController(); + + expect(this.vm.filteredOrderables).toEqual(this.orderables); + }); + + it('should expose this.selectProductsModalService.reject method', function() { + this.initController(); + + expect(this.vm.close).toBe(this.selectProductsModalService.reject); + }); + + it('should expose this.selectProductsModalService.resolve method', function() { + this.initController(); + + expect(this.vm.selectProducts).toBe(this.selectProductsModalService.resolve); + }); + + it('should initialize selection object', function() { + this.initController(); + + expect(this.vm.selections).toEqual(this.selections); + }); + + it('should show all for empty filter', function() { + this.$stateParams.search = ''; + + this.initController(); + + this.vm.isUnpackKitState = true; + + expect(this.vm.filteredOrderables).toEqual(this.orderables); + }); + + it('should show all for undefined', function() { + this.$stateParams.search = undefined; + + this.initController(); + + this.vm.isUnpackKitState = true; + + expect(this.vm.filteredOrderables).toEqual(this.orderables); + }); + + it('should show all for null', function() { + this.$stateParams.search = null; + + this.initController(); + + this.vm.isUnpackKitState = true; + + expect(this.vm.filteredOrderables).toEqual(this.orderables); + }); + + it('should only return codes starting with the search text', function() { + this.$stateParams.search = 'Ps'; + + this.initController(); + + this.vm.isUnpackKitState = true; + + expect(this.vm.filteredOrderables).toEqual([this.orderables[1]]); + + this.$stateParams.search = '1'; + + this.initController(); + + expect(this.vm.filteredOrderables).toEqual([]); + }); + + it('should only return defined full product name', function() { + this.$stateParams.search = 'mC1'; + + this.initController(); + + this.vm.isUnpackKitState = true; + + expect(this.orderables[4].withFullProductName).toBeUndefined(); + }); + + it('should only return defined product codes', function() { + this.$stateParams.search = 'mC1'; + + this.initController(); + + this.vm.isUnpackKitState = true; + + expect(this.orderables[4].withProductCode).toBeUndefined(); + }); + + it('should return result for search text of both product codes and full product name', function() { + this.$stateParams.search = 'co'; + + this.initController(); + + this.vm.isUnpackKitState = true; + + expect(this.vm.filteredOrderables).toEqual([this.orderables[5], this.orderables[6]]); + + }); + + it('should return empty list if no matches found', function() { + this.$stateParams.search = 'po'; + + this.initController(); + + this.vm.isUnpackKitState = true; + + expect(this.vm.filteredOrderables).toEqual([]); + + }); + + it('should return result with either product code or full product name', function() { + this.$stateParams.search = 'ame'; + + this.initController(); + + this.vm.isUnpackKitState = true; + + expect(this.vm.filteredOrderables).toEqual([this.orderables[8]]); + + this.$stateParams.search = 'disp'; + + this.initController(); + + expect(this.vm.filteredOrderables[0].fullProductName).toBeDefined(); + expect(this.vm.filteredOrderables[0].productCode).toBeUndefined(); + }); + + }); + + describe('search', function() { + + it('should reload state without notifying on unpack kit screen', function() { + this.initController(); + + this.vm.code = 'C100'; + this.vm.name = 'Levora'; + this.vm.isUnpackKitState = true; + + this.vm.search(); + + expect(this.$state.go).toHaveBeenCalledWith('.', { + productCode: 'C100', + productName: 'Levora' + }, { + reload: '', + notify: false + }); + }); + + it('should reload every other state than unpack kit with search param', function() { + this.initController(); + + this.vm.searchText = 'new search'; + + this.vm.search(); + + expect(this.$state.go).toHaveBeenCalledWith('.', { + search: 'new search' + }); + }); + + }); + +}); diff --git a/src/select-products-modal/select-products-modal.html b/src/select-products-modal/select-products-modal.html new file mode 100644 index 00000000..f02f31bf --- /dev/null +++ b/src/select-products-modal/select-products-modal.html @@ -0,0 +1,84 @@ + diff --git a/src/select-products-modal/select-products-modal.module.js b/src/select-products-modal/select-products-modal.module.js new file mode 100644 index 00000000..deee7ff9 --- /dev/null +++ b/src/select-products-modal/select-products-modal.module.js @@ -0,0 +1,36 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +(function() { + + 'use strict'; + + /** + * @module select-products-modal + * + * @description + * Provides modal for selecting products. + */ + angular.module('select-products-modal', [ + 'openlmis-auth', + 'openlmis-form', + 'openlmis-pagination', + 'openlmis-templates', + 'openlmis-modal', + 'openlmis-modal-state', + 'referencedata-orderable' + ]); + +})(); diff --git a/src/select-products-modal/select-products-modal.service.js b/src/select-products-modal/select-products-modal.service.js new file mode 100644 index 00000000..a45b710e --- /dev/null +++ b/src/select-products-modal/select-products-modal.service.js @@ -0,0 +1,135 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +(function() { + + 'use strict'; + + /** + * @ngdoc service + * @name select-products-modal.selectProductsModalService + * + * @description + * Modal for selecting products. + */ + angular + .module('select-products-modal') + .service('selectProductsModalService', service); + + service.$inject = ['$q', '$state']; + + function service($q, $state) { + var deferred, + selections, + products; + + this.show = show; + this.getSelections = getSelections; + this.getOrderables = getOrderables; + this.resolve = resolve; + this.reject = reject; + + /** + * @ngdoc method + * @methodOf select-products-modal.selectProductsModalService + * @name show + * + * @description + * Opens a modal responsible for selecting products and cleans out searching. + * + * @param {Array} products the list of available products + * @return {promise} the promise resolving to a list of selected products + */ + function show(config) { + deferred = $q.defer(); + + products = config ? config.products : undefined; + selections = config && config.selections ? angular.copy(config.selections) : {}; + + var stateParams = {}; + stateParams.productName = undefined; + stateParams.productCode = undefined; + $state.go('.addOrderables', stateParams, { + notify: false + }); + + return deferred.promise; + } + + /** + * @ngdoc method + * @methodOf select-products-modal.selectProductsModalService + * @name getSelections + * + * @description + * Returns a list of selected products. + * + * @return {promise} the promise resolving to a list of selected products + */ + function getSelections() { + return selections; + } + + /** + * @ngdoc method + * @methodOf select-products-modal.selectProductsModalService + * @name getOrderables + * + * @description + * Returns all products. + * + * @return {promise} the promise resolving to a list of all products + */ + function getOrderables() { + return products; + } + + /** + * @ngdoc method + * @methodOf select-products-modal.selectProductsModalService + * @name resolve + * + * @description + * Resolves selected products. Returns to the parent state without reloading it. + * + * @param {Array} products the list of available products + * @return {promise} the promise resolving to a list of selected products + */ + function resolve() { + $state.go('^', {}, { + notify: false + }); + deferred.resolve(_.values(selections).filter(function(selection) { + return selection; + })); + } + + /** + * @ngdoc method + * @methodOf select-products-modal.selectProductsModalService + * @name reject + * + * @description + * Rejects changes. Returns to the parent state without reloading it. + */ + function reject() { + $state.go('^', {}, { + notify: false + }); + deferred.reject(); + } + } + +})(); diff --git a/src/select-products-modal/select-products-modal.service.spec.js b/src/select-products-modal/select-products-modal.service.spec.js new file mode 100644 index 00000000..5516366d --- /dev/null +++ b/src/select-products-modal/select-products-modal.service.spec.js @@ -0,0 +1,122 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +describe('selectProductsModalService', function() { + + beforeEach(function() { + module('select-products-modal'); + + inject(function($injector) { + this.$q = $injector.get('$q'); + this.$rootScope = $injector.get('$rootScope'); + this.$state = $injector.get('$state'); + this.selectProductsModalService = $injector.get('selectProductsModalService'); + this.OrderableDataBuilder = $injector.get('OrderableDataBuilder'); + }); + + this.orderables = [ + new this.OrderableDataBuilder().buildJson(), + new this.OrderableDataBuilder().buildJson(), + new this.OrderableDataBuilder().buildJson(), + new this.OrderableDataBuilder().buildJson(), + new this.OrderableDataBuilder().buildJson() + ]; + + this.config = {}; + this.config.products = this.orderables; + + spyOn(this.$state, 'go'); + }); + + describe('show', function() { + + it('it should redirect to .addOrderables state', function() { + this.selectProductsModalService.show(this.config); + + expect(this.$state.go).toHaveBeenCalledWith('.addOrderables', { + code: undefined, + name: undefined + }, { + notify: false + }); + }); + + it('it should return selections if any product is selected', function() { + this.config.selections = [this.orderables[0], this.orderables[2], this.orderables[4]]; + this.selectProductsModalService.show(this.config); + + expect(this.selectProductsModalService.getSelections()).toEqual(this.config.selections); + }); + + it('it should return empty object if none of the products is selected', function() { + this.selectProductsModalService.show(this.config); + + expect(this.selectProductsModalService.getSelections()).toEqual({}); + }); + + it('it should return products if any product is selected', function() { + this.selectProductsModalService.show(this.config); + + expect(this.selectProductsModalService.getOrderables()).toEqual(this.config.products); + }); + + it('it should return undefined if none of the products is selected', function() { + this.config.products = undefined; + this.selectProductsModalService.show(this.config); + + expect(this.selectProductsModalService.getOrderables()).toBeUndefined(); + }); + + }); + + describe('resolve', function() { + + it('it should resolve selected products', function() { + this.config.selections = [this.orderables[0], this.orderables[2], this.orderables[4]]; + this.selectProductsModalService.show(this.config); + + this.selectProductsModalService.resolve(); + this.$rootScope.$apply(); + + expect(this.$state.go).toHaveBeenCalledWith('^', { + code: undefined, + name: undefined + }, { + notify: false + }); + }); + + }); + + describe('reject', function() { + + it('it should reject selected products', function() { + this.config.selections = [this.orderables[0], this.orderables[2], this.orderables[4]]; + this.selectProductsModalService.show(this.config); + + this.selectProductsModalService.reject(); + this.$rootScope.$apply(); + + expect(this.$state.go).toHaveBeenCalledWith('^', { + code: undefined, + name: undefined + }, { + notify: false + }); + }); + + }); + +}); From dec4029dd428a22f944de4bd5dda3d4d5b609662 Mon Sep 17 00:00:00 2001 From: mdulko Date: Tue, 24 Sep 2024 11:41:00 +0200 Subject: [PATCH 2/5] OLMIS-7987: Move Submit Requisitionless Orders functionalities from Angola to Core instance From f46d8cc1797c8dadb3da589fb97eead786b77793 Mon Sep 17 00:00:00 2001 From: mdulko Date: Tue, 24 Sep 2024 15:21:35 +0200 Subject: [PATCH 3/5] OLMIS-7987: Move Submit Requisitionless Orders functionalities from Angola to Core instance --- src/common/_layout.mixins.scss | 7 + src/common/_mixins.scss | 305 +++++++++++++++++++++++++++++++++ 2 files changed, 312 insertions(+) create mode 100644 src/common/_layout.mixins.scss create mode 100644 src/common/_mixins.scss diff --git a/src/common/_layout.mixins.scss b/src/common/_layout.mixins.scss new file mode 100644 index 00000000..ddbd3511 --- /dev/null +++ b/src/common/_layout.mixins.scss @@ -0,0 +1,7 @@ +@mixin flex-layout($driection, $justify-content: normal, $align-items: normal, $gap: 0) { + display: flex; + flex-direction: $driection; + justify-content: $justify-content; + align-items: $align-items; + gap: $gap; +} \ No newline at end of file diff --git a/src/common/_mixins.scss b/src/common/_mixins.scss new file mode 100644 index 00000000..669719d7 --- /dev/null +++ b/src/common/_mixins.scss @@ -0,0 +1,305 @@ +@mixin fade-in { + @keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } +} + +@mixin fade-out { + @keyframes fadeOut { + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } + } +} + +@mixin direction() { + [dir="ltr"] & { + direction: ltr; + } + [dir="rtl"] & { + direction: rtl; + } +} + +@mixin translate-x($value) { + [dir="ltr"] & { + transform: translateX($value); + } + [dir="rtl"] & { + transform: translateX(-$value); + } +} + +@mixin translate-xy($valueX, $valueY) { + [dir="ltr"] & { + transform: translateX($valueX) translateY($valueY); + } + [dir="rtl"] & { + transform: translateX(-$valueX) translateY($valueY); + } +} + +@mixin padding-right($value) { + [dir="ltr"] & { + padding-right: $value; + } + [dir="rtl"] & { + padding-left: $value; + } +} + +@mixin padding-right-important($value) { + [dir="ltr"] & { + padding-right: $value !important; + } + [dir="rtl"] & { + padding-left: $value !important; + } +} + +@mixin padding-left($value) { + [dir="ltr"] & { + padding-left: $value; + } + [dir="rtl"] & { + padding-right: $value; + } +} + +@mixin padding-x($value) { + [dir="ltr"] & { + padding-left: $value; + padding-right: $value; + } + [dir="rtl"] & { + padding-left: $value; + padding-right: $value; + } +} + +@mixin padding-yx($padding-y, $padding-x) { + padding-top: $padding-y; + padding-bottom: $padding-y; + + [dir="ltr"] & { + padding-left: $padding-x; + padding-right: $padding-x; + } + [dir="rtl"] & { + padding-left: $padding-x; + padding-right: $padding-x; + } +} + +@mixin padding($padding-top, $padding-right, $padding-bottom, $padding-left) { + padding-top: $padding-top; + padding-bottom: $padding-bottom; + + [dir="ltr"] & { + padding-left: $padding-left; + padding-right: $padding-right; + } + [dir="rtl"] & { + padding-left: $padding-right; + padding-right: $padding-left; + } +} + +@mixin margin-right($value) { + [dir="ltr"] & { + margin-right: $value; + } + [dir="rtl"] & { + margin-left: $value; + } +} + +@mixin margin-left($value) { + [dir="ltr"] & { + margin-left: $value; + } + [dir="rtl"] & { + margin-right: $value; + } +} + +@mixin margin-x($value) { + [dir="ltr"] & { + margin-left: $value; + margin-right: $value; + } + [dir="rtl"] & { + margin-left: $value; + margin-right: $value; + } +} + +@mixin margin-yx($margin-y, $margin-x) { + margin-top: $margin-y; + margin-bottom: $margin-y; + + [dir="ltr"] & { + margin-left: $margin-x; + margin-right: $margin-x; + } + [dir="rtl"] & { + margin-left: $margin-x; + margin-right: $margin-x; + } +} + +@mixin margin($margin-top, $margin-right, $margin-bottom, $margin-left) { + margin-top: $margin-top; + margin-bottom: $margin-bottom; + + [dir="ltr"] & { + margin-left: $margin-left; + margin-right: $margin-right; + } + [dir="rtl"] & { + margin-left: $margin-right; + margin-right: $margin-left; + } +} + +@mixin border-right($value) { + [dir="ltr"] & { + border-right: $value; + } + [dir="rtl"] & { + border-left: $value; + } +} + +@mixin border-right-important($value) { + [dir="ltr"] & { + border-right: $value !important; + } + [dir="rtl"] & { + border-left: $value !important; + } +} + +@mixin border-left($value) { + [dir="ltr"] & { + border-left: $value; + } + [dir="rtl"] & { + border-right: $value; + } +} + +@mixin border-left-important($value) { + [dir="ltr"] & { + border-left: $value !important; + } + [dir="rtl"] & { + border-right: $value !important; + } +} + +@mixin border-x($value) { + [dir="ltr"] & { + border-left: $value; + border-right: $value; + } + [dir="rtl"] & { + border-left: $value; + border-right: $value; + } +} + +@mixin border-yx($border-y, $border-x) { + border-top: $border-y; + border-bottom: $border-y; + + [dir="ltr"] & { + border-left: $border-x; + border-right: $border-x; + } + [dir="rtl"] & { + border-left: $border-x; + border-right: $border-x; + } +} + +@mixin border($border-top, $border-right, $border-bottom, $border-left) { + border-top: $border-top; + border-bottom: $border-bottom; + + [dir="ltr"] & { + border-left: $border-left; + border-right: $border-right; + } + [dir="rtl"] & { + border-left: $border-right; + border-right: $border-left; + } +} + +@mixin float($float) { + $float-ltr: $float; + $float-rtl: if($float == left, right, if($float == right, left, none)); + + [dir="ltr"] & { + float: $float-ltr; + } + + [dir="rtl"] & { + float: $float-rtl; + } +} + +@mixin right($value) { + [dir="ltr"] & { + right: $value; + } + + [dir="rtl"] & { + left: $value; + } +} + +@mixin left($value) { + [dir="ltr"] & { + left: $value; + } + + [dir="rtl"] & { + right: $value; + } +} + +@mixin text-align($alignment) { + $alignment-ltr: $alignment; + $alignment-rtl: if($alignment == left, right, if($alignment == right, left, $alignment)); + + [dir="ltr"] & { + text-align: $alignment-ltr; + } + + [dir="rtl"] & { + text-align: $alignment-rtl; + } +} + +@mixin background-position($position) { + $position-ltr: $position; + $position-rtl: if($position == left, right, if($position == right, left, $position)); + + [dir="ltr"] & { + background-position: $position-ltr; + } + + [dir="rtl"] & { + background-position: $position-rtl; + } +} From 68ea07c9e33440343f70052245a6ee8b6935f28f Mon Sep 17 00:00:00 2001 From: mdulko Date: Tue, 24 Sep 2024 15:29:27 +0200 Subject: [PATCH 4/5] OLMIS-7987: Move Submit Requisitionless Orders functionalities from Angola to Core instance --- src/select-products-modal/messages_en.json | 14 - .../select-products-modal-state.provider.js | 124 -------- ...lect-products-modal-state.provider.spec.js | 223 -------------- .../select-products-modal.controller.js | 114 ------- .../select-products-modal.controller.spec.js | 278 ------------------ .../select-products-modal.html | 84 ------ .../select-products-modal.module.js | 36 --- .../select-products-modal.service.js | 135 --------- .../select-products-modal.service.spec.js | 122 -------- 9 files changed, 1130 deletions(-) delete mode 100644 src/select-products-modal/messages_en.json delete mode 100644 src/select-products-modal/select-products-modal-state.provider.js delete mode 100644 src/select-products-modal/select-products-modal-state.provider.spec.js delete mode 100644 src/select-products-modal/select-products-modal.controller.js delete mode 100644 src/select-products-modal/select-products-modal.controller.spec.js delete mode 100644 src/select-products-modal/select-products-modal.html delete mode 100644 src/select-products-modal/select-products-modal.module.js delete mode 100644 src/select-products-modal/select-products-modal.service.js delete mode 100644 src/select-products-modal/select-products-modal.service.spec.js diff --git a/src/select-products-modal/messages_en.json b/src/select-products-modal/messages_en.json deleted file mode 100644 index 2bd409fb..00000000 --- a/src/select-products-modal/messages_en.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "selectProductsModal.addProducts": "Add Products", - "selectProductsModal.searchProduct": "Search Product", - "selectProductsModal.search": "Search", - "selectProductsModal.select": "Select", - "selectProductsModal.code": "Code", - "selectProductsModal.product": "Product", - "selectProductsModal.uoi": "Unit of Issue", - "selectProductsModal.cancel": "Cancel", - "selectProductsModal.addProducts.emptyList": "You have not selected any products.", - "selectProductsModal.productCode": "Product Code", - "selectProductsModal.productName": "Product Name", - "selectProductsModal.searchByCodeInfo": "Please type in a part of the product name or the first few letters of the product code." -} \ No newline at end of file diff --git a/src/select-products-modal/select-products-modal-state.provider.js b/src/select-products-modal/select-products-modal-state.provider.js deleted file mode 100644 index 4f07d703..00000000 --- a/src/select-products-modal/select-products-modal-state.provider.js +++ /dev/null @@ -1,124 +0,0 @@ -/* - * This program is part of the OpenLMIS logistics management information system platform software. - * Copyright © 2017 VillageReach - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - *   - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  - * See the GNU Affero General Public License for more details. You should have received a copy of - * the GNU Affero General Public License along with this program. If not, see - * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  - */ - -(function() { - - 'use strict'; - - /** - * @ngdoc service - * @name select-products-modal.selectProductsModalState - * - * @description - * Provider for defining states which should be displayed as modals. - */ - angular - .module('select-products-modal') - .provider('selectProductsModalState', selectProductsModalStateProvider); - - selectProductsModalStateProvider.$inject = ['modalStateProvider', '$stateProvider']; - - function selectProductsModalStateProvider(modalStateProvider, $stateProvider) { - this.stateWithAddOrderablesChildState = stateWithAddOrderablesChildState; - this.$get = [function() {}]; - - /** - * @ngdoc method - * @methodOf select-products-modal.selectProductsModalState - * @name state - * - * @description - * Defines a state which should be displayed as modal. Currently the resolves from parent - * states are not available in the controller by default. To make them available please - * include them in the parentResolves parameter line this - * - * ``` - * selectProductsModalStateProvider.state('some.state', { - * parentResolves: ['someParentResolve'] - * }); - * ``` - * - * @param {String} stateName the name of the state - * @param {Object} state the state definition - */ - function stateWithAddOrderablesChildState(stateName, state) { - - if (state.display === 'modal') { - modalStateProvider.state(stateName, state); - } else { - $stateProvider.state(stateName, state); - } - - modalStateProvider - .state(stateName + '.addOrderables', { - controller: 'SelectProductsModalController', - controllerAs: 'vm', - templateUrl: 'select-products-modal/select-products-modal.html', - label: 'adminOrderableEdit.kitUnpackList', - nonTrackable: true, - params: { - addOrderablesPage: undefined, - addOrderablesSize: undefined, - productName: undefined, - productCode: undefined, - search: undefined - }, - resolve: { - external: function(selectProductsModalService) { - return !selectProductsModalService.getOrderables(); - }, - isUnpackKitState: function() { - return stateName === 'openlmis.administration.orderables.edit.kitUnpackList.edit'; - }, - orderables: function(OrderableResource, paginationService, $stateParams, - selectProductsModalService, isUnpackKitState) { - var orderables = selectProductsModalService.getOrderables(); - - if (orderables) { - return paginationService.registerList(undefined, $stateParams, function() { - return orderables; - }, { - paginationId: 'addOrderables' - }); - } - return paginationService.registerUrl($stateParams, function(stateParams) { - var params; - if (isUnpackKitState) { - params = { - sort: 'fullProductName,asc', - page: stateParams.page, - size: stateParams.size, - code: stateParams.productCode, - name: stateParams.productName - }; - } else { - params = { - sort: 'fullProductName,asc', - page: stateParams.page, - size: stateParams.size, - search: stateParams.search - }; - } - return new OrderableResource().query(params); - }, { - paginationId: 'addOrderables' - }); - } - } - }); - } - } - -})(); diff --git a/src/select-products-modal/select-products-modal-state.provider.spec.js b/src/select-products-modal/select-products-modal-state.provider.spec.js deleted file mode 100644 index 728d27b0..00000000 --- a/src/select-products-modal/select-products-modal-state.provider.spec.js +++ /dev/null @@ -1,223 +0,0 @@ -/* - * This program is part of the OpenLMIS logistics management information system platform software. - * Copyright © 2017 VillageReach - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - *   - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  - * See the GNU Affero General Public License for more details. You should have received a copy of - * the GNU Affero General Public License along with this program. If not, see - * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  - */ - -describe('selectProductsModalStateProvider', function() { - - beforeEach(function() { - var context = this; - module('openlmis-modal-state', function(modalStateProvider, $stateProvider) { - context.modalStateProvider = modalStateProvider; - context.$stateProvider = $stateProvider; - }); - - module('select-products-modal', function(selectProductsModalStateProvider) { - context.selectProductsModalStateProvider = selectProductsModalStateProvider; - }); - - module('openlmis-pagination'); - - inject(function($injector) { - this.selectProductsModalService = $injector.get('selectProductsModalService'); - this.OrderableResource = $injector.get('OrderableResource'); - this.OrderableDataBuilder = $injector.get('OrderableDataBuilder'); - this.PageDataBuilder = $injector.get('PageDataBuilder'); - this.$q = $injector.get('$q'); - this.$rootScope = $injector.get('$rootScope'); - this.$state = $injector.get('$state'); - this.$controller = $injector.get('$controller'); - this.paginationService = $injector.get('paginationService'); - }); - - this.orderables = [ - new this.OrderableDataBuilder().build(), - new this.OrderableDataBuilder().build() - ]; - - this.orderablesPage = new this.PageDataBuilder() - .withContent(this.orderables) - .build(); - - this.$scope = this.$rootScope.$new(); - - this.paginationController = this.$controller('PaginationController', { - $scope: this.$scope, - paginationService: this.paginationService - }); - - spyOn(this.OrderableResource.prototype, 'query').andReturn(this.$q.resolve(this.orderablesPage)); - spyOn(this.selectProductsModalService, 'getOrderables').andReturn(); - spyOn(this.selectProductsModalService, 'getSelections').andReturn({}); - - spyOn(this.modalStateProvider, 'state').andCallFake(function(stateName, config) { - context.config = config; - context.stateName = stateName; - return context; - }); - - this.goToState = function() { - this.$state.go.apply(this, arguments); - this.$rootScope.$apply(); - }; - - this.getResolvedValue = function(name) { - return this.$state.$current.locals.globals[name]; - }; - }); - - describe('stateWithAddOrderablesChildState', function() { - - beforeEach(function() { - spyOn(this.$stateProvider, 'state').andReturn(); - }); - - it('should register the state as page if no display is defined', function() { - this.selectProductsModalStateProvider.stateWithAddOrderablesChildState('main', { - url: '/main' - }); - - expect(this.$stateProvider.state).toHaveBeenCalledWith('main', { - url: '/main' - }); - }); - - it('should register the state as modal if display is set to modal', function() { - this.selectProductsModalStateProvider.stateWithAddOrderablesChildState('mainModal', { - url: '/mainModal', - display: 'modal' - }); - - expect(this.modalStateProvider.state).toHaveBeenCalledWith('mainModal', { - url: '/mainModal', - display: 'modal' - }); - }); - - }); - - describe('.addOrderables', function() { - - beforeEach(function() { - this.selectProductsModalStateProvider.stateWithAddOrderablesChildState('main', { - url: '/main' - }); - - }); - - it('should set controller', function() { - expect(this.config.controller).toBe('SelectProductsModalController'); - }); - - it('should set controllerAs', function() { - expect(this.config.controllerAs).toBe('vm'); - }); - - it('should set templateUrl', function() { - expect(this.config.templateUrl).toBe('select-products-modal/select-products-modal.html'); - }); - - it('should set label', function() { - expect(this.config.label).toBe('adminOrderableEdit.kitUnpackList'); - }); - - it('should set nonTrackable', function() { - expect(this.config.nonTrackable).toBeTruthy(); - }); - - it('should set params', function() { - expect(this.config.params.addOrderablesPage).toBeUndefined(); - expect(this.config.params.addOrderablesSize).toBeUndefined(); - expect(this.config.params.productName).toBeUndefined(); - expect(this.config.params.productCode).toBeUndefined(); - }); - - it('should resolve external as false if no orderables provided', function() { - this.selectProductsModalService.getOrderables.andReturn(this.orderables); - - var result = this.config.resolve.external(this.selectProductsModalService); - - expect(result).toBeFalsy(); - expect(this.selectProductsModalService.getOrderables).toHaveBeenCalled(); - }); - - it('should resolve external as true if any orderable provided', function() { - this.selectProductsModalService.getOrderables.andReturn(); - - var result = this.config.resolve.external(this.selectProductsModalService); - - expect(result).toBeTruthy(); - expect(this.selectProductsModalService.getOrderables).toHaveBeenCalled(); - }); - - it('should resolve orderables for external pagination', function() { - this.selectProductsModalService.getOrderables.andReturn(this.orderables); - - this.config.resolve.orderables(this.OrderableResource, this.paginationService, - this.$state.params, this.selectProductsModalService); - - expect(this.selectProductsModalService.getOrderables).toHaveBeenCalled(); - }); - - it('should resolve matching orderables on unpack kit screen', function() { - this.nameParam = 'Product'; - this.codeParam = 'C100'; - this.$state.params = { - page: 0, - size: 10, - productName: this.nameParam, - productCode: this.codeParam - }; - - this.selectProductsModalService.getOrderables.andReturn(); - - this.config.resolve.orderables(this.OrderableResource, this.paginationService, - this.$state.params, this.selectProductsModalService, true); - - expect(this.selectProductsModalService.getOrderables).toHaveBeenCalled(); - - expect(this.OrderableResource.prototype.query).toHaveBeenCalledWith({ - sort: 'fullProductName,asc', - page: 0, - size: 10, - name: this.nameParam, - code: this.codeParam - }); - }); - - it('should resolve matching orderables on any screen except unpack kit', function() { - this.search = 'search text'; - this.$state.params = { - page: 0, - size: 10, - search: this.search - }; - - this.selectProductsModalService.getOrderables.andReturn(); - - this.config.resolve.orderables(this.OrderableResource, this.paginationService, - this.$state.params, this.selectProductsModalService, false); - - expect(this.selectProductsModalService.getOrderables).toHaveBeenCalled(); - - expect(this.OrderableResource.prototype.query).toHaveBeenCalledWith({ - sort: 'fullProductName,asc', - page: 0, - size: 10, - search: this.search - }); - }); - - }); - -}); diff --git a/src/select-products-modal/select-products-modal.controller.js b/src/select-products-modal/select-products-modal.controller.js deleted file mode 100644 index 5a86813b..00000000 --- a/src/select-products-modal/select-products-modal.controller.js +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This program is part of the OpenLMIS logistics management information system platform software. - * Copyright © 2017 VillageReach - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - *   - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  - * See the GNU Affero General Public License for more details. You should have received a copy of - * the GNU Affero General Public License along with this program. If not, see - * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  - */ - -(function() { - - 'use strict'; - - /** - * @ngdoc controller - * @name select-products-modal.controller:SelectProductsModalController - * - * @description - * Manages Select Products Modal. - */ - angular - .module('select-products-modal') - .controller('SelectProductsModalController', controller); - - controller.$inject = ['orderables', '$state', 'selectProductsModalService', 'external', '$stateParams', - 'isUnpackKitState']; - - function controller(orderables, $state, selectProductsModalService, external, $stateParams, - isUnpackKitState) { - var vm = this; - - vm.$onInit = onInit; - vm.selectProducts = selectProductsModalService.resolve; - vm.close = selectProductsModalService.reject; - vm.search = search; - - /** - * @ngdoc method - * @methodOf select-products-modal.controller:SelectProductsModalController - * @name $onInit - * - * @description - * Initialization method of the SelectProductsModalController. - */ - function onInit() { - vm.orderables = orderables; - vm.selections = selectProductsModalService.getSelections(); - vm.selected = _.mapObject(vm.selections, function(selection) { - return !!selection; - }); - vm.external = external; - vm.code = $stateParams.productCode; - vm.name = $stateParams.productName; - vm.searchText = $stateParams.search; - vm.filteredOrderables = filterOrderables(orderables, $stateParams.search); - vm.isUnpackKitState = isUnpackKitState; - } - - /** - * @ngdoc method - * @methodOf select-products-modal.controller:SelectProductsModalController - * @name search - * - * @description - * Refreshes the product list so the add product dialog box shows only relevant products - * without reloading parent state. - */ - function search() { - if (vm.isUnpackKitState) { - var stateParams = angular.copy($stateParams); - stateParams.productCode = vm.code; - stateParams.productName = vm.name; - $state.go('.', stateParams, { - reload: $state.$current.name, - notify: false - }); - } else { - $state.go('.', _.extend({}, $stateParams, { - search: vm.searchText - })); - } - } - - function filterOrderables(orderables, searchText) { - if (searchText) { - return orderables.filter(searchByCodeAndName); - } - return orderables; - } - - function searchByCodeAndName(orderable) { - var searchText = vm.searchText.toLowerCase(); - var foundInFullProductName; - var foundInProductCode; - - if (orderable.productCode !== undefined) { - foundInProductCode = orderable.productCode.toLowerCase().startsWith(searchText); - } - - if (orderable.fullProductName !== undefined) { - foundInFullProductName = orderable.fullProductName.toLowerCase().contains(searchText); - } - return foundInFullProductName || foundInProductCode; - } - - } - -})(); diff --git a/src/select-products-modal/select-products-modal.controller.spec.js b/src/select-products-modal/select-products-modal.controller.spec.js deleted file mode 100644 index 1cde4bb0..00000000 --- a/src/select-products-modal/select-products-modal.controller.spec.js +++ /dev/null @@ -1,278 +0,0 @@ -/* - * This program is part of the OpenLMIS logistics management information system platform software. - * Copyright © 2017 VillageReach - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - *   - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  - * See the GNU Affero General Public License for more details. You should have received a copy of - * the GNU Affero General Public License along with this program. If not, see - * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  - */ - -describe('SelectProductsModalController', function() { - - beforeEach(function() { - module('select-products-modal'); - - inject(function($injector) { - this.$q = $injector.get('$q'); - this.$rootScope = $injector.get('$rootScope'); - this.$controller = $injector.get('$controller'); - this.alertService = $injector.get('alertService'); - this.OrderableDataBuilder = $injector.get('OrderableDataBuilder'); - this.selectProductsModalService = $injector.get('selectProductsModalService'); - this.$state = $injector.get('$state'); - }); - - this.external = false; - - this.orderables = [ - new this.OrderableDataBuilder() - .withFullProductName('Product One') - .withProductCode('PC1') - .build(), - new this.OrderableDataBuilder() - .withFullProductName('Product Two pc2') - .withProductCode('PS1') - .build(), - new this.OrderableDataBuilder() - .withFullProductName('Product Three') - .withProductCode('XB1') - .build(), - new this.OrderableDataBuilder() - .withFullProductName('Product Four') - .withProductCode('N64') - .build(), - new this.OrderableDataBuilder() - .withFullProductName('undefined') - .withProductCode('undefined') - .build(), - new this.OrderableDataBuilder() - .withFullProductName('Counter Something') - .withProductCode('Co1') - .build(), - new this.OrderableDataBuilder() - .withFullProductName('Another product') - .withProductCode('Code Name') - .build(), - new this.OrderableDataBuilder() - .withFullProductName('Some Product') - .withProductCode('CD1') - .build(), - new this.OrderableDataBuilder() - .withFullProductName('Same Product to displayed') - .withoutProductCode() - .build() - ]; - - this.selections = {}; - this.selections[this.orderables[0].id] = this.orderables[0]; - this.$stateParams = {}; - - spyOn(this.$state, 'go'); - spyOn(this.selectProductsModalService, 'getSelections').andReturn(this.selections); - - this.initController = function() { - this.vm = this.$controller('SelectProductsModalController', { - modalDeferred: this.modalDeferred, - orderables: this.orderables, - external: this.external, - $stateParams: this.$stateParams, - isUnpackKitState: false - }); - this.vm.$onInit(); - }; - }); - - describe('$onInit', function() { - - it('should expose orderables', function() { - this.initController(); - - expect(this.vm.orderables).toEqual(this.orderables); - }); - - it('should expose code', function() { - this.$stateParams.productCode = 'C100'; - this.initController(); - - expect(this.vm.code).toEqual(this.$stateParams.productCode); - }); - - it('should expose name', function() { - this.$stateParams.productName = 'Product'; - this.initController(); - - expect(this.vm.name).toEqual(this.$stateParams.productName); - }); - - it('should expose filteredOrderables', function() { - this.initController(); - - expect(this.vm.filteredOrderables).toEqual(this.orderables); - }); - - it('should expose this.selectProductsModalService.reject method', function() { - this.initController(); - - expect(this.vm.close).toBe(this.selectProductsModalService.reject); - }); - - it('should expose this.selectProductsModalService.resolve method', function() { - this.initController(); - - expect(this.vm.selectProducts).toBe(this.selectProductsModalService.resolve); - }); - - it('should initialize selection object', function() { - this.initController(); - - expect(this.vm.selections).toEqual(this.selections); - }); - - it('should show all for empty filter', function() { - this.$stateParams.search = ''; - - this.initController(); - - this.vm.isUnpackKitState = true; - - expect(this.vm.filteredOrderables).toEqual(this.orderables); - }); - - it('should show all for undefined', function() { - this.$stateParams.search = undefined; - - this.initController(); - - this.vm.isUnpackKitState = true; - - expect(this.vm.filteredOrderables).toEqual(this.orderables); - }); - - it('should show all for null', function() { - this.$stateParams.search = null; - - this.initController(); - - this.vm.isUnpackKitState = true; - - expect(this.vm.filteredOrderables).toEqual(this.orderables); - }); - - it('should only return codes starting with the search text', function() { - this.$stateParams.search = 'Ps'; - - this.initController(); - - this.vm.isUnpackKitState = true; - - expect(this.vm.filteredOrderables).toEqual([this.orderables[1]]); - - this.$stateParams.search = '1'; - - this.initController(); - - expect(this.vm.filteredOrderables).toEqual([]); - }); - - it('should only return defined full product name', function() { - this.$stateParams.search = 'mC1'; - - this.initController(); - - this.vm.isUnpackKitState = true; - - expect(this.orderables[4].withFullProductName).toBeUndefined(); - }); - - it('should only return defined product codes', function() { - this.$stateParams.search = 'mC1'; - - this.initController(); - - this.vm.isUnpackKitState = true; - - expect(this.orderables[4].withProductCode).toBeUndefined(); - }); - - it('should return result for search text of both product codes and full product name', function() { - this.$stateParams.search = 'co'; - - this.initController(); - - this.vm.isUnpackKitState = true; - - expect(this.vm.filteredOrderables).toEqual([this.orderables[5], this.orderables[6]]); - - }); - - it('should return empty list if no matches found', function() { - this.$stateParams.search = 'po'; - - this.initController(); - - this.vm.isUnpackKitState = true; - - expect(this.vm.filteredOrderables).toEqual([]); - - }); - - it('should return result with either product code or full product name', function() { - this.$stateParams.search = 'ame'; - - this.initController(); - - this.vm.isUnpackKitState = true; - - expect(this.vm.filteredOrderables).toEqual([this.orderables[8]]); - - this.$stateParams.search = 'disp'; - - this.initController(); - - expect(this.vm.filteredOrderables[0].fullProductName).toBeDefined(); - expect(this.vm.filteredOrderables[0].productCode).toBeUndefined(); - }); - - }); - - describe('search', function() { - - it('should reload state without notifying on unpack kit screen', function() { - this.initController(); - - this.vm.code = 'C100'; - this.vm.name = 'Levora'; - this.vm.isUnpackKitState = true; - - this.vm.search(); - - expect(this.$state.go).toHaveBeenCalledWith('.', { - productCode: 'C100', - productName: 'Levora' - }, { - reload: '', - notify: false - }); - }); - - it('should reload every other state than unpack kit with search param', function() { - this.initController(); - - this.vm.searchText = 'new search'; - - this.vm.search(); - - expect(this.$state.go).toHaveBeenCalledWith('.', { - search: 'new search' - }); - }); - - }); - -}); diff --git a/src/select-products-modal/select-products-modal.html b/src/select-products-modal/select-products-modal.html deleted file mode 100644 index f02f31bf..00000000 --- a/src/select-products-modal/select-products-modal.html +++ /dev/null @@ -1,84 +0,0 @@ - diff --git a/src/select-products-modal/select-products-modal.module.js b/src/select-products-modal/select-products-modal.module.js deleted file mode 100644 index deee7ff9..00000000 --- a/src/select-products-modal/select-products-modal.module.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This program is part of the OpenLMIS logistics management information system platform software. - * Copyright © 2017 VillageReach - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - *   - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  - * See the GNU Affero General Public License for more details. You should have received a copy of - * the GNU Affero General Public License along with this program. If not, see - * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  - */ - -(function() { - - 'use strict'; - - /** - * @module select-products-modal - * - * @description - * Provides modal for selecting products. - */ - angular.module('select-products-modal', [ - 'openlmis-auth', - 'openlmis-form', - 'openlmis-pagination', - 'openlmis-templates', - 'openlmis-modal', - 'openlmis-modal-state', - 'referencedata-orderable' - ]); - -})(); diff --git a/src/select-products-modal/select-products-modal.service.js b/src/select-products-modal/select-products-modal.service.js deleted file mode 100644 index a45b710e..00000000 --- a/src/select-products-modal/select-products-modal.service.js +++ /dev/null @@ -1,135 +0,0 @@ -/* - * This program is part of the OpenLMIS logistics management information system platform software. - * Copyright © 2017 VillageReach - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - *   - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  - * See the GNU Affero General Public License for more details. You should have received a copy of - * the GNU Affero General Public License along with this program. If not, see - * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  - */ - -(function() { - - 'use strict'; - - /** - * @ngdoc service - * @name select-products-modal.selectProductsModalService - * - * @description - * Modal for selecting products. - */ - angular - .module('select-products-modal') - .service('selectProductsModalService', service); - - service.$inject = ['$q', '$state']; - - function service($q, $state) { - var deferred, - selections, - products; - - this.show = show; - this.getSelections = getSelections; - this.getOrderables = getOrderables; - this.resolve = resolve; - this.reject = reject; - - /** - * @ngdoc method - * @methodOf select-products-modal.selectProductsModalService - * @name show - * - * @description - * Opens a modal responsible for selecting products and cleans out searching. - * - * @param {Array} products the list of available products - * @return {promise} the promise resolving to a list of selected products - */ - function show(config) { - deferred = $q.defer(); - - products = config ? config.products : undefined; - selections = config && config.selections ? angular.copy(config.selections) : {}; - - var stateParams = {}; - stateParams.productName = undefined; - stateParams.productCode = undefined; - $state.go('.addOrderables', stateParams, { - notify: false - }); - - return deferred.promise; - } - - /** - * @ngdoc method - * @methodOf select-products-modal.selectProductsModalService - * @name getSelections - * - * @description - * Returns a list of selected products. - * - * @return {promise} the promise resolving to a list of selected products - */ - function getSelections() { - return selections; - } - - /** - * @ngdoc method - * @methodOf select-products-modal.selectProductsModalService - * @name getOrderables - * - * @description - * Returns all products. - * - * @return {promise} the promise resolving to a list of all products - */ - function getOrderables() { - return products; - } - - /** - * @ngdoc method - * @methodOf select-products-modal.selectProductsModalService - * @name resolve - * - * @description - * Resolves selected products. Returns to the parent state without reloading it. - * - * @param {Array} products the list of available products - * @return {promise} the promise resolving to a list of selected products - */ - function resolve() { - $state.go('^', {}, { - notify: false - }); - deferred.resolve(_.values(selections).filter(function(selection) { - return selection; - })); - } - - /** - * @ngdoc method - * @methodOf select-products-modal.selectProductsModalService - * @name reject - * - * @description - * Rejects changes. Returns to the parent state without reloading it. - */ - function reject() { - $state.go('^', {}, { - notify: false - }); - deferred.reject(); - } - } - -})(); diff --git a/src/select-products-modal/select-products-modal.service.spec.js b/src/select-products-modal/select-products-modal.service.spec.js deleted file mode 100644 index 5516366d..00000000 --- a/src/select-products-modal/select-products-modal.service.spec.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This program is part of the OpenLMIS logistics management information system platform software. - * Copyright © 2017 VillageReach - * - * This program is free software: you can redistribute it and/or modify it under the terms - * of the GNU Affero General Public License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - *   - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  - * See the GNU Affero General Public License for more details. You should have received a copy of - * the GNU Affero General Public License along with this program. If not, see - * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  - */ - -describe('selectProductsModalService', function() { - - beforeEach(function() { - module('select-products-modal'); - - inject(function($injector) { - this.$q = $injector.get('$q'); - this.$rootScope = $injector.get('$rootScope'); - this.$state = $injector.get('$state'); - this.selectProductsModalService = $injector.get('selectProductsModalService'); - this.OrderableDataBuilder = $injector.get('OrderableDataBuilder'); - }); - - this.orderables = [ - new this.OrderableDataBuilder().buildJson(), - new this.OrderableDataBuilder().buildJson(), - new this.OrderableDataBuilder().buildJson(), - new this.OrderableDataBuilder().buildJson(), - new this.OrderableDataBuilder().buildJson() - ]; - - this.config = {}; - this.config.products = this.orderables; - - spyOn(this.$state, 'go'); - }); - - describe('show', function() { - - it('it should redirect to .addOrderables state', function() { - this.selectProductsModalService.show(this.config); - - expect(this.$state.go).toHaveBeenCalledWith('.addOrderables', { - code: undefined, - name: undefined - }, { - notify: false - }); - }); - - it('it should return selections if any product is selected', function() { - this.config.selections = [this.orderables[0], this.orderables[2], this.orderables[4]]; - this.selectProductsModalService.show(this.config); - - expect(this.selectProductsModalService.getSelections()).toEqual(this.config.selections); - }); - - it('it should return empty object if none of the products is selected', function() { - this.selectProductsModalService.show(this.config); - - expect(this.selectProductsModalService.getSelections()).toEqual({}); - }); - - it('it should return products if any product is selected', function() { - this.selectProductsModalService.show(this.config); - - expect(this.selectProductsModalService.getOrderables()).toEqual(this.config.products); - }); - - it('it should return undefined if none of the products is selected', function() { - this.config.products = undefined; - this.selectProductsModalService.show(this.config); - - expect(this.selectProductsModalService.getOrderables()).toBeUndefined(); - }); - - }); - - describe('resolve', function() { - - it('it should resolve selected products', function() { - this.config.selections = [this.orderables[0], this.orderables[2], this.orderables[4]]; - this.selectProductsModalService.show(this.config); - - this.selectProductsModalService.resolve(); - this.$rootScope.$apply(); - - expect(this.$state.go).toHaveBeenCalledWith('^', { - code: undefined, - name: undefined - }, { - notify: false - }); - }); - - }); - - describe('reject', function() { - - it('it should reject selected products', function() { - this.config.selections = [this.orderables[0], this.orderables[2], this.orderables[4]]; - this.selectProductsModalService.show(this.config); - - this.selectProductsModalService.reject(); - this.$rootScope.$apply(); - - expect(this.$state.go).toHaveBeenCalledWith('^', { - code: undefined, - name: undefined - }, { - notify: false - }); - }); - - }); - -}); From 16e45a1db865a176e41774b4e01b7bce887d9aa9 Mon Sep 17 00:00:00 2001 From: mdulko Date: Tue, 24 Sep 2024 15:39:18 +0200 Subject: [PATCH 5/5] OLMIS-7987: Move Submit Requisitionless Orders functionalities from Angola to Core instance --- src/requisition-order-create/order-create-summary-modal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/requisition-order-create/order-create-summary-modal.jsx b/src/requisition-order-create/order-create-summary-modal.jsx index f9ff725e..84755d0a 100644 --- a/src/requisition-order-create/order-create-summary-modal.jsx +++ b/src/requisition-order-create/order-create-summary-modal.jsx @@ -1,5 +1,5 @@ import React, { useMemo, useState } from 'react'; -import Modal from '../react-components/modals/modal'; +import Modal from '../react-components/modals/Modal'; import EditableTable from '../react-components/table/editable-table'; import { orderTableColumns } from './order-create.constant'; import TabNavigation from '../react-components/tab-navigation/tab-navigation';