Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Palvelukokonaisuudet (Service Units) #430

Merged
merged 29 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0d7e05a
Add service units to redux store
juhakujala Jun 23, 2022
49b32ea
Get users service units to redux store
juhakujala Jun 23, 2022
97a5633
Add user active service unit selection to top navigation
juhakujala Jun 23, 2022
1492ae8
Add service unit filtering for select elements that require it
juhakujala Jun 23, 2022
c242ae6
FormField: Add support for passing service unit for form fields
juhakujala Jun 23, 2022
655f054
Contacts: Add support for service units
juhakujala Jun 23, 2022
9781e6a
Leases: Add support for service units
juhakujala Jun 23, 2022
1dcad59
Invoice Notes: Add support for service units
juhakujala Jun 23, 2022
003b525
SAP Invoices: Add support for service units
juhakujala Jun 23, 2022
e5f267c
Infill development: Add support for service units
juhakujala Jun 23, 2022
fb3fe1d
Update some of the FormNames enum values
juhakujala Jun 23, 2022
aea3347
service units to user pems tests
NC-jsAhonen Oct 16, 2023
3e4aa91
service unit id: small cleanup
NC-jsAhonen Nov 7, 2023
a175aed
simplify filtering by active service unit logic
NC-jsAhonen Nov 7, 2023
a291b07
statistic reports: multiselect for service units
NC-jsAhonen Nov 8, 2023
163734f
statistic reports: parse query for service units
NC-jsAhonen Nov 8, 2023
8e14683
simplify if statements in invoice note list page
NC-jsAhonen Nov 10, 2023
218dd2d
simplify service unit check in some permissions
NC-jsAhonen Nov 21, 2023
0bdc05c
combine if statements in create lease form
NC-jsAhonen Nov 21, 2023
f3cf372
simplify null check in user service unit select
NC-jsAhonen Nov 21, 2023
5f9c152
user service unit select: change filter into find
NC-jsAhonen Nov 22, 2023
4a9e70f
leasing summary: add internal_order field
NC-jsAhonen Dec 14, 2023
ab49291
wip: filter receivable types by service unit
NC-jsAhonen Dec 20, 2023
ff68b95
edit internal order if there is permission
NC-jsAhonen Jan 8, 2024
de1a3d0
internal_order: not exactly but max 12 chars
NC-jsAhonen Jan 8, 2024
bfdb9f3
fix receivable type filtering in create charge
NC-jsAhonen Jan 10, 2024
af72fdc
filter receivable types by service unit: add unit test
NC-jsAhonen Jan 10, 2024
a244916
filter area search by service unit
NC-jsAhonen Mar 6, 2024
b9907c5
temp: loader for receivable type field to prevent crash
NC-jsAhonen Apr 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/app/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import {getEpochTime} from '$util/helpers';
import {getError} from '$src/api/selectors';
import {getApiToken, getApiTokenExpires, getIsFetching, getLoggedInUser} from '$src/auth/selectors';
import {getLinkUrl, getPageTitle, getShowSearch} from '$components/topNavigation/selectors';
import {getUserGroups} from '$src/usersPermissions/selectors';
import {getUserGroups, getUserActiveServiceUnit, getUserServiceUnits} from '$src/usersPermissions/selectors';
import {setRedirectUrlToSessionStorage} from '$util/storage';

import type {ApiError} from '$src/api/types';
import type {ApiToken} from '$src/auth/types';
import type {UserGroups} from '$src/usersPermissions/types';
import type {RootState} from '$src/root/types';
import type {ApiError} from '../api/types';
import type {ApiToken} from '../auth/types';
import type {UserGroups, UserServiceUnit, UserServiceUnits} from '$src/usersPermissions/types';
import type {RootState} from '../root/types';

const url = window.location.toString();
const IS_DEVELOPMENT_URL = url.includes('ninja') || url.includes('localhost');
Expand All @@ -53,6 +53,8 @@ type Props = {
linkUrl: string,
location: Object,
pageTitle: string,
userActiveServiceUnit: UserServiceUnit,
userServiceUnits: UserServiceUnits,
showSearch: boolean,
user: Object,
userGroups: UserGroups,
Expand Down Expand Up @@ -178,6 +180,8 @@ class App extends Component<Props, State> {
showSearch,
user,
userGroups,
userActiveServiceUnit,
userServiceUnits,
} = this.props;
const {displaySideMenu} = this.state;
const appStyle = (IS_DEVELOPMENT_URL) ? 'app-dev' : 'app';
Expand Down Expand Up @@ -260,6 +264,7 @@ class App extends Component<Props, State> {
transitionOut="fadeOut"
closeOnToastrClick={true}
/>

<TopNavigation
isMenuOpen={displaySideMenu}
linkUrl={linkUrl}
Expand All @@ -269,6 +274,8 @@ class App extends Component<Props, State> {
toggleSideMenu={this.toggleSideMenu}
userGroups={userGroups}
username={get(user, 'profile.name')}
userServiceUnits={userServiceUnits}
userActiveServiceUnit={userActiveServiceUnit}
/>

<section className="app__content">
Expand Down Expand Up @@ -311,6 +318,8 @@ const mapStateToProps = (state: RootState) => {
showSearch: getShowSearch(state),
user,
userGroups: getUserGroups(state),
userServiceUnits: getUserServiceUnits(state),
userActiveServiceUnit: getUserActiveServiceUnit(state),
};
};

Expand Down
48 changes: 43 additions & 5 deletions src/areaSearch/components/AreaSearchApplicationListPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ import type {Attributes, Methods as MethodsType} from '$src/types';
import type {ApiResponse} from '$src/types';
import type {UsersPermissions as UsersPermissionsType} from '$src/usersPermissions/types';
import AreaSearchExportModal from '$src/areaSearch/components/AreaSearchExportModal';
import { getUserActiveServiceUnit } from "../../usersPermissions/selectors";
import type { UserServiceUnit } from "../../usersPermissions/types";

const VisualizationTypes = {
MAP: 'map',
Expand Down Expand Up @@ -106,6 +108,7 @@ type Props = {
lastEditError: any,
change: Function,
selectedSearches: Object,
userActiveServiceUnit: UserServiceUnit
}

type State = {
Expand All @@ -125,6 +128,7 @@ type State = {

class AreaSearchApplicationListPage extends PureComponent<Props, State> {
_isMounted: boolean
_hasFetchedAreaSearches: boolean;

state: State = {
properties: [],
Expand All @@ -139,6 +143,7 @@ class AreaSearchApplicationListPage extends PureComponent<Props, State> {
isEditModalOpen: false,
isExportModalOpen: false,
editModalTargetAreaSearch: null,
userActiveServiceUnit: undefined,
}

static contextTypes = {
Expand Down Expand Up @@ -356,7 +361,7 @@ class AreaSearchApplicationListPage extends PureComponent<Props, State> {
};

search = () => {
const {fetchAreaSearchList, location: {search}} = this.props;
const {fetchAreaSearchList, location: {search}, userActiveServiceUnit} = this.props;
const searchQuery = getUrlParams(search);
const page = searchQuery.page ? Number(searchQuery.page) : 1;

Expand All @@ -365,6 +370,11 @@ class AreaSearchApplicationListPage extends PureComponent<Props, State> {
}

searchQuery.limit = LIST_TABLE_PAGE_SIZE;

if (searchQuery.service_unit === undefined && userActiveServiceUnit) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Abstract there are 3 tabs (own work, own unit's work, and all area searches). Does this functionality allow user to see all area searches from all service units by default?

searchQuery.service_unit = userActiveServiceUnit.id;
}

delete searchQuery.page;
delete searchQuery.in_bbox;
delete searchQuery.visualization;
Expand All @@ -374,7 +384,7 @@ class AreaSearchApplicationListPage extends PureComponent<Props, State> {
}

searchByBBox = () => {
const {fetchAreaSearchListByBBox, location: {search}} = this.props;
const {fetchAreaSearchListByBBox, location: {search}, userActiveServiceUnit} = this.props;
const searchQuery = getUrlParams(search);
const leaseStates = this.getSearchStates(searchQuery);

Expand All @@ -388,6 +398,10 @@ class AreaSearchApplicationListPage extends PureComponent<Props, State> {
searchQuery.lease_state = leaseStates;
}

if (searchQuery.service_unit === undefined && userActiveServiceUnit) {
searchQuery.service_unit = userActiveServiceUnit.id;
}

searchQuery.limit = 10000;
delete searchQuery.page;
delete searchQuery.visualization;
Expand Down Expand Up @@ -480,11 +494,29 @@ class AreaSearchApplicationListPage extends PureComponent<Props, State> {
}

componentDidUpdate(prevProps) {
const {location: {search: currentSearch}, isEditingAreaSearch, lastEditError} = this.props;
const {location: {search: prevSearch}} = prevProps;
const {location: {search: currentSearch}, isEditingAreaSearch, lastEditError, userActiveServiceUnit} = this.props;
const {location: {search: prevSearch}, userActiveServiceUnit: prevUserActiveServiceUnit} = prevProps;
const {visualizationType} = this.state;
const searchQuery = getUrlParams(currentSearch);

const handleSearch = () => {
this.setSearchFormValues();
this.search();
};

if(userActiveServiceUnit) {

if(!this._hasFetchedAreaSearches) { // No search has been done yet
handleSearch();
this._hasFetchedAreaSearches = true;

} else if(userActiveServiceUnit !== prevUserActiveServiceUnit
&& !currentSearch.includes('service_unit')) {
// Search again after changing user active service unit only if not explicitly setting the service unit filter
handleSearch();
}
}

if ((currentSearch !== prevSearch) ||
(!isEditingAreaSearch && !lastEditError && prevProps.isEditingAreaSearch)) {
this.closeAreaSearchEditModal();
Expand Down Expand Up @@ -514,6 +546,7 @@ class AreaSearchApplicationListPage extends PureComponent<Props, State> {
componentWillUnmount() {
window.removeEventListener('popstate', this.handlePopState);
this._isMounted = false;
this._hasFetchedAreaSearches = false;
}

handlePopState = () => {
Expand Down Expand Up @@ -546,7 +579,7 @@ class AreaSearchApplicationListPage extends PureComponent<Props, State> {
}

setSearchFormValues = () => {
const {location: {search}, initializeForm} = this.props;
const {location: {search}, initializeForm, userActiveServiceUnit} = this.props;
const searchQuery = getUrlParams(search);
const page = searchQuery.page ? Number(searchQuery.page) : 1;
const states = this.getSearchStates(searchQuery);
Expand All @@ -565,6 +598,10 @@ class AreaSearchApplicationListPage extends PureComponent<Props, State> {
delete initialValues.visualization;
delete initialValues.zoom;

if(initialValues.service_unit === undefined && userActiveServiceUnit) {
initialValues.service_unit = userActiveServiceUnit.id;
}

await initializeForm(FormNames.AREA_SEARCH_SEARCH, initialValues);
};

Expand Down Expand Up @@ -799,6 +836,7 @@ export default (flowRight(
isEditingAreaSearch: getIsEditingAreaSearch(state),
lastEditError: getLastAreaSearchEditError(state),
selectedSearches: selector(state, 'selectedSearches'),
userActiveServiceUnit: getUserActiveServiceUnit(state),
};
},
{
Expand Down
26 changes: 26 additions & 0 deletions src/areaSearch/components/search/Search.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type State = {
areaSearches: ApiResponse,
areaSearchAttributes: Attributes,
attributes: Attributes,
serviceUnitOptions: Array<Object>,
}

class Search extends Component<Props, State> {
Expand All @@ -64,6 +65,7 @@ class Search extends Component<Props, State> {
areaSearches: null,
areaSearchAttributes: {},
attributes: {},
serviceUnitOptions: []
};

componentDidMount() {
Expand Down Expand Up @@ -148,6 +150,7 @@ class Search extends Component<Props, State> {
if (props.areaSearchAttributes !== state.areaSearchAttributes) {
newState.intendedUseOptions = getFieldOptions(props.areaSearchAttributes, AreaSearchFieldPaths.INTENDED_USE);
newState.lessorOptions = getFieldOptions(props.areaSearchAttributes, AreaSearchFieldPaths.LESSOR);
newState.serviceUnitOptions = getFieldOptions(props.areaSearchAttributes, 'service_unit', true);
}

return !isEmpty(newState) ? newState : null;
Expand All @@ -163,6 +166,7 @@ class Search extends Component<Props, State> {
isBasicSearch,
intendedUseOptions,
lessorOptions,
serviceUnitOptions,
} = this.state;

return (
Expand Down Expand Up @@ -459,6 +463,28 @@ class Search extends Component<Props, State> {
/>
</SearchInputColumn>
</SearchRow>
<SearchRow>
<SearchLabelColumn>
<SearchLabel>Palvelukokonaisuus</SearchLabel>
</SearchLabelColumn>
<SearchInputColumn>
<FormField
autoBlur
disableDirty
fieldAttributes={{
label: 'Palvelukokonaisuus',
type: FieldTypes.CHOICE,
read_only: false,
}}
invisibleLabel
name='service_unit'
overrideValues={{
options: serviceUnitOptions,
}}
/>
</SearchInputColumn>
</SearchRow>

</Column>
</Row>
</Fragment>
Expand Down
2 changes: 1 addition & 1 deletion src/components/attributes/LeaseInvoiceTabAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ function LeaseInvoiceTabAttributes(WrappedComponent: any) {
fetchLeaseCreateChargeAttributes();
}

if(!isFetchingReceivableTypes && !receivableTypes) {
if(!isFetchingReceivableTypes) {
fetchReceivableTypes();
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/components/form/FieldTypeContactSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import {getContentContact} from '$src/contacts/helpers';
import {addEmptyOption, sortStringByKeyAsc} from '$util/helpers';
import {fetchContacts} from '$src/contacts/requestsAsync';

import type {UserServiceUnit} from '$src/usersPermissions/types';

type Props = {
disabled?: boolean,
displayError: boolean,
input: Object,
isDirty: boolean,
onChange: Function,
placeholder?: string,
serviceUnit: UserServiceUnit,
}

const FieldTypeContactSelect = ({
Expand All @@ -23,11 +26,13 @@ const FieldTypeContactSelect = ({
isDirty,
onChange,
placeholder,
serviceUnit,
}: Props): React$Node => {
const getContacts = debounce(async(inputValue: string, callback: Function) => {
const contacts = await fetchContacts({
search: inputValue,
limit: 20,
service_unit: serviceUnit?.id || "",
});

callback(addEmptyOption(contacts.map((lessor) => getContentContact(lessor)).sort((a, b) => sortStringByKeyAsc(a, b, 'label'))));
Expand Down
5 changes: 5 additions & 0 deletions src/components/form/FieldTypeLeaseSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import {getContentLeaseOption} from '$src/leases/helpers';
import {addEmptyOption, sortStringByKeyAsc} from '$util/helpers';
import {fetchLeases} from '$src/leases/requestsAsync';

import type {UserServiceUnit} from '$src/usersPermissions/types';

type Props = {
disabled?: boolean,
displayError: boolean,
input: Object,
isDirty: boolean,
onChange: Function,
placeholder?: string,
serviceUnit: UserServiceUnit,
}

const FieldTypeLeaseSelect = ({
Expand All @@ -23,12 +26,14 @@ const FieldTypeLeaseSelect = ({
isDirty,
onChange,
placeholder,
serviceUnit,
}: Props): React$Node => {
const getLeases = debounce(async(inputValue: string, callback: Function) => {
const leases = await fetchLeases({
succinct: true,
identifier: inputValue,
limit: 15,
service_unit: serviceUnit?.id || "",
});

callback(addEmptyOption(leases.map((lease) => getContentLeaseOption(lease)).sort((a, b) => sortStringByKeyAsc(a, b, 'label'))));
Expand Down
5 changes: 5 additions & 0 deletions src/components/form/FieldTypeLessorSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import {getContentLessor} from '$src/lessor/helpers';
import {addEmptyOption, sortStringByKeyAsc} from '$util/helpers';
import {fetchContacts} from '$src/contacts/requestsAsync';

import type {UserServiceUnit} from '$src/usersPermissions/types';

type Props = {
disabled?: boolean,
displayError: boolean,
input: Object,
isDirty: boolean,
onChange: Function,
placeholder?: string,
serviceUnit: UserServiceUnit,
}

const FieldTypeLessorSelect = ({
Expand All @@ -23,11 +26,13 @@ const FieldTypeLessorSelect = ({
isDirty,
onChange,
placeholder,
serviceUnit,
}: Props): React$Node => {
const getLessors = debounce(async(inputValue: string, callback: Function) => {
const lessors = await fetchContacts({
is_lessor: true,
search: inputValue,
service_unit: serviceUnit?.id || "",
});

callback(addEmptyOption(lessors.map((lessor) => getContentLessor(lessor)).sort((a, b) => sortStringByKeyAsc(a, b, 'label'))));
Expand Down
Loading