diff --git a/src/config/chis-ke/config.json b/src/config/chis-ke/config.json
index e409dd3..8d91f3b 100644
--- a/src/config/chis-ke/config.json
+++ b/src/config/chis-ke/config.json
@@ -215,6 +215,7 @@
"user_role": ["community_health_assistant"],
"username_from_place": false,
"deactivate_users_on_replace": false,
+ "can_assign_multiple": true,
"hierarchy": [
{
"friendly_name": "Sub County",
diff --git a/src/config/index.ts b/src/config/index.ts
index 5b2bd4d..2ba940d 100644
--- a/src/config/index.ts
+++ b/src/config/index.ts
@@ -26,6 +26,7 @@ export type ContactType = {
place_properties: ContactProperty[];
contact_properties: ContactProperty[];
deactivate_users_on_replace: boolean;
+ can_assign_multiple?: boolean;
hint?: string;
};
diff --git a/src/liquid/new/create_form.liquid b/src/liquid/new/create_form.liquid
index eaed2dd..dcdce11 100644
--- a/src/liquid/new/create_form.liquid
+++ b/src/liquid/new/create_form.liquid
@@ -1,15 +1,14 @@
-
+
@@ -55,7 +57,4 @@
{% render 'new/place_list.liquid', places: entry, contactType: contactType %}
{% endfor %}
{% endif %}
-
diff --git a/src/liquid/new/create_place.liquid b/src/liquid/new/create_place.liquid
index 73d87e0..eec0ff4 100644
--- a/src/liquid/new/create_place.liquid
+++ b/src/liquid/new/create_place.liquid
@@ -4,13 +4,19 @@
New
{{ contactType.contact_friendly }}
-
-
+
+
-
+
{% render 'new/create_form.liquid', contactType: contactType, hierarchy: hierarchy, data: data, places: places %}
diff --git a/src/routes/add-place.ts b/src/routes/add-place.ts
index 6f228ab..2f25ef3 100644
--- a/src/routes/add-place.ts
+++ b/src/routes/add-place.ts
@@ -8,6 +8,7 @@ import RemotePlaceResolver from '../lib/remote-place-resolver';
import { UploadManager } from '../services/upload-manager';
import RemotePlaceCache from '../lib/remote-place-cache';
import WarningSystem from '../warnings';
+import semver from 'semver';
export default async function addPlace(fastify: FastifyInstance) {
fastify.get('/add-place', async (req, resp) => {
@@ -17,6 +18,12 @@ export default async function addPlace(fastify: FastifyInstance) {
const contactType = queryParams.type
? Config.getContactType(queryParams.type)
: contactTypes[contactTypes.length - 1];
+
+ if (semver.gte(req.chtSession.chtCoreVersion, '4.9.0') && contactType.can_assign_multiple) {
+ resp.redirect(`/new?place_type=${queryParams.type}`);
+ return;
+ }
+
const op = queryParams.op || 'new';
const tmplData = {
view: 'add',
diff --git a/src/routes/app.ts b/src/routes/app.ts
index 96411dd..2a59dcd 100644
--- a/src/routes/app.ts
+++ b/src/routes/app.ts
@@ -95,7 +95,7 @@ export default async function sessionCache(fastify: FastifyInstance) {
const directiveModel = new DirectiveModel(sessionCache, req.cookies.filter);
const chtApi = new ChtApi(req.chtSession);
- uploadManager.doUpload(sessionCache.getPlaces(), chtApi, ignoreWarnings === 'true');
+ uploadManager.doUpload(sessionCache.getPlaces(), chtApi, true);
return resp.view('src/liquid/place/directive.html', {
directiveModel
diff --git a/src/routes/new.ts b/src/routes/new.ts
index 1587b56..c5ba7a2 100644
--- a/src/routes/new.ts
+++ b/src/routes/new.ts
@@ -4,8 +4,8 @@ import { ChtApi } from '../lib/cht-api';
import SessionCache from '../services/session-cache';
import _ from 'lodash';
import PlaceFactory from '../services/place-factory';
-import { UploadManager } from '../services/upload-manager';
import Validation from '../validation';
+import { PlaceUploadState } from '../services/place';
export default async function newHandler(fastify: FastifyInstance) {
@@ -15,37 +15,36 @@ export default async function newHandler(fastify: FastifyInstance) {
const sessionCache: SessionCache = req.sessionCache;
const grouped = Object.values(
- _.groupBy(sessionCache.getPlaces({ type: contactType.name }), (p) => p.contact.id)
- ).filter(p => p.length > 0).reverse();
+ _.groupBy(sessionCache.getPlaces({ type: contactType.name }).filter(p => p.state === PlaceUploadState.STAGED), (p) => p.contact.id)
+ )
+ .filter(p => p.length > 0)
+ .reverse();
+
const data = {
hierarchy: Config.getHierarchyWithReplacement(contactType, 'desc'),
contactType,
- places: grouped
+ places: grouped,
+ logo: Config.getLogoBase64()
};
return resp.view('src/liquid/new/create_place.liquid', data);
});
fastify.post('/new', async (req, resp) => {
- const { place_type } = req.query as any;
+ const { place_type, cont } = req.query as any;
+
const contactType = Config.getContactType(place_type);
const chtApi = new ChtApi(req.chtSession);
const sessionCache: SessionCache = req.sessionCache;
- const uploadManager: UploadManager = fastify.uploadManager;
- const places = await PlaceFactory.createManyWithSingleUser(req.body as {[key:string]:string}, contactType, sessionCache, chtApi);
- uploadManager.uploadWithSingleUser(places, chtApi);
-
- const grouped = Object.values(
- _.groupBy(sessionCache.getPlaces({ type: contactType.name }), (p) => p.contact.id)
- ).filter(p => p.length > 0).reverse();
- const data = {
- hierarchy: Config.getHierarchyWithReplacement(contactType, 'desc'),
- contactType,
- places: grouped
- };
-
- return resp.view('src/liquid/new/create_form.liquid', data);
+ await PlaceFactory.createManyWithSingleUser(req.body as {[key:string]:string}, contactType, sessionCache, chtApi);
+
+ if (cont) {
+ resp.header('HX-Redirect', `/new?place_type=${place_type}`);
+ } else {
+ resp.header('HX-Redirect', `/`);
+ }
+ return;
});
fastify.get('/place_form', async (req, resp) => {
diff --git a/src/services/upload-manager.ts b/src/services/upload-manager.ts
index 4b3d260..aebecc0 100644
--- a/src/services/upload-manager.ts
+++ b/src/services/upload-manager.ts
@@ -3,7 +3,7 @@ import EventEmitter from 'events';
import * as RetryLogic from '../lib/retry-logic';
import { ChtApi, CreatedPlaceResult, PlacePayload } from '../lib/cht-api';
import { Config } from '../config';
-import Place, { PlaceUploadState } from './place';
+import Place, { PlaceUploadState, UserCreationDetails } from './place';
import RemotePlaceCache from '../lib/remote-place-cache';
import { UploadNewPlace } from './upload.new';
import { UploadReplacementWithDeletion } from './upload.replacement';
@@ -21,20 +21,25 @@ export interface Uploader {
export class UploadManager extends EventEmitter {
doUpload = async (places: Place[], chtApi: ChtApi, ignoreWarnings: boolean = false) => {
const placesNeedingUpload = places.filter(p => {
- return !p.isCreated && !p.hasValidationErrors && !p.hasSharedUser && (ignoreWarnings || !p.warnings.length);
+ return !p.isCreated && !p.hasValidationErrors && (ignoreWarnings || !p.warnings.length);
});
this.eventedPlaceStateChange(placesNeedingUpload, PlaceUploadState.SCHEDULED);
- const independants = placesNeedingUpload.filter(p => !p.isDependant);
- const dependants = placesNeedingUpload.filter(p => p.isDependant);
+ const independants = placesNeedingUpload.filter(p => !p.isDependant && !p.hasSharedUser);
+ const dependants = placesNeedingUpload.filter(p => p.isDependant && !p.hasSharedUser);
+
await this.uploadPlacesInBatches(independants, chtApi);
await this.uploadPlacesInBatches(dependants, chtApi);
+ await this.uploadGrouped(placesNeedingUpload, chtApi);
};
- uploadWithSingleUser = async (places: Place[], api: ChtApi) => {
+ uploadGrouped = async (places: Place[], api: ChtApi) => {
const grouped = _.groupBy(places, place => place.contact.id);
Object.keys(grouped).forEach(async k => {
- await this.uploadGroup(grouped[k], api);
+ const places = grouped[k];
+ if (places.length > 1) {
+ await this.uploadGroup(places[0].creationDetails, places.slice(1), api);
+ }
});
};
@@ -89,34 +94,33 @@ export class UploadManager extends EventEmitter {
}
}
- private async uploadGroup(places: Place[], api: ChtApi) {
- let contactId;
- const placeIds: string[] = [];
- for (let i = 0; i < places.length; i++) {
- const place = places[i];
+ private async uploadGroup(creationDetails: UserCreationDetails, places: Place[], api: ChtApi) {
+ if (!creationDetails.username || !creationDetails.placeId) {
+ throw new Error('creationDetails must not be empty');
+ }
+ const placeIds: {[key:string]: string} = { [creationDetails.placeId]: '' };
+ for (const place of places) {
this.eventedPlaceStateChange(place, PlaceUploadState.IN_PROGRESS);
- const payload = place.asChtPayload(api.chtSession.username, contactId);
+ const payload = place.asChtPayload(api.chtSession.username, creationDetails.contactId);
await Config.mutate(payload, api, place.isReplacement);
const result = await api.createPlace(payload);
- if (!contactId) {
- if (!result.contactId) {
- throw new Error('expected contact id');
- }
- contactId = result.contactId;
- }
+
place.creationDetails.contactId = result.contactId;
- placeIds.push(result.placeId);
+ place.creationDetails.placeId = result.placeId;
+ placeIds[result.placeId] = result.placeId;
}
- const userPayload = new UserPayload(places[0], placeIds, contactId!);
- const { username, password } = await RetryLogic.createUserWithRetries(userPayload, api);
+
+ await api.updateUser({ username: creationDetails.username!, place: Object.keys(placeIds)});
const created_at = new Date().getTime();
places.forEach(place => {
- place.creationDetails.username = username;
- place.creationDetails.password = password;
+ place.creationDetails.username = creationDetails.username;
+ place.creationDetails.password = creationDetails.password;
place.creationDetails.created_at = created_at;
+
this.eventedPlaceStateChange(place, PlaceUploadState.SUCCESS);
});
- this.emit('refresh_grouped', contactId);
+
+ this.emit('refresh_grouped', creationDetails.contactId);
}
public triggerRefresh(place_id: string) {