Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
87 changes: 64 additions & 23 deletions server/api/controller/cron.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,11 @@ async function fetchTreePermit(crawlMethod){

const fetchPlanStatus = () => {
var mavatStatus = null;
const mavatStatusList = [];
var mavatStatusApprove, mavatStatusCancel, mavatStatusObjection, mavatStatusDone;
const planStatusLimit = Config.get('planStatusChange.limit');
Log.info('plan limit:', planStatusLimit);

const lastVisitedDifference = Config.get('planStatusChange.lastVisitedDifference'); //days
const timeDifference = moment.duration(lastVisitedDifference, 'd');
const date = moment().subtract(timeDifference);
Expand All @@ -414,6 +417,7 @@ const fetchPlanStatus = () => {
qb.leftJoin('status_mapping', 'plan.status', '=' ,'status_mapping.mavat_status')
.whereRaw(`(status_mapping.meirim_status is null or status_mapping.meirim_status != '${meirimStatuses.APPROVED}') and plan.MP_ID NOT LIKE \'NOT_FOUND\' and (plan.last_visited_status < '${dateString}' OR plan.last_visited_status IS NULL)`)
.orderBy('plan.last_visited_status','asc');

qb.limit(planStatusLimit);
})
.fetchAll()
Expand Down Expand Up @@ -442,19 +446,34 @@ const fetchPlanStatus = () => {
await plan.save({ 'last_visited_status': now });
return;
}
const mostRecentStatus = mostRecent[0].attributes.status;

if (mavatStatus === null) {
const res = await PlanStatusChange.getAllStatuses();
mavatStatus = createStatusMap(res[0], mavatStatusList);
mavatStatusApprove = mavatStatus.get(meirimStatuses.APPROVED);
mavatStatusCancel = mavatStatus.get(meirimStatuses.CANCELLED);
mavatStatusObjection = mavatStatus.get(meirimStatuses.PUBLIC_OBJECTION);
mavatStatusDone = mavatStatusApprove.concat(mavatStatusCancel);
}

const mostRecentStatus = getLatestExistingStatus(mostRecent, mavatStatusList);
if (mostRecentStatus === null) {
mostRecentStatus = mostRecent[0].attributes.status;
}
Log.debug('updating plan status to:', mostRecentStatus);
await plan.save({ 'last_visited_status': now , 'status': mostRecentStatus });

// save all plan statuses into plan_status_change table
await PlanStatusChange.savePlanStatusChange(planStatuses);

// get all relevant mavat status for deposited plan
if (mavatStatus === null) {
const res = await PlanStatusChange.byMeirimStatus(meirimStatuses.PUBLIC_OBJECTION);
mavatStatus = res[0].map(rec => rec.mavat_status);
if (Config.get('email.send_plan_deposit_email') && plan.attributes.status_email === 0) {
await sendEmailIfNeeded(plan, planStatuses, mavatStatusObjection, meirimStatuses.PUBLIC_OBJECTION);
}
if (Config.get('email.send_plan_end_email') && plan.attributes.status_email < 3) {
await sendEmailIfNeeded(plan, planStatuses, mavatStatusDone,
mavatStatusCancel.indexOf(mostRecentStatus) >= 0 ? meirimStatuses.CANCELLED : meirimStatuses.APPROVED);
}
await sendEmailIfNeeded(plan, planStatuses, mavatStatus);
}
catch (err) {
Log.error('Error:', err.message);
Expand All @@ -472,45 +491,67 @@ const fetchPlanStatus = () => {
));
};

function getLatestExistingStatus(planStatuses, mavatStatusList) {
for (var i = 0; i < planStatuses.length; ++i) {
var status = planStatuses[i].attributes.status;
if (mavatStatusList.indexOf(status) >= 0) {
return status;
}
}
return null;
}

function createStatusMap(res, mavatStatusList) {
const groupedMap = new Map();
for (const e of res) {
if (!groupedMap.has(e.meirim_status)) {
groupedMap.set(e.meirim_status, []);
}
groupedMap.get(e.meirim_status).push(e.mavat_status);
mavatStatusList.push(e.mavat_status);
}
return groupedMap;
}

/**
* Check if plan is now in deposited status and send email to watching users
* @param {*} plan
* @param {*} planStatuses - plan statuses
* @param {*} mavatStatus - all mavat status for deposited plan
* @param {*} mavatStatus - all relevant mavat status for plan
* @param {*} meirimStatus - plan meirim status
*/
async function sendEmailIfNeeded(plan, planStatuses, mavatStatus) {
if (Config.get('email.send_plan_deposit_email') !== true) {
return false;
}
var sendEmail = shouldSendEmail(plan, planStatuses, mavatStatus);
async function sendEmailIfNeeded(plan, planStatuses, mavatStatus, meirimStatus) {
var sendEmail = shouldSendEmail(planStatuses, mavatStatus);
if (sendEmail) {
PlanPerson.getUsersForPlan(plan.get('id'))
.then(users => {
if (!users[0] || !users[0].length) {
return sendEmail;
}
return Bluebird.mapSeries(users[0], user =>
Email.planDepositAlert(user, plan)
);
return Bluebird.mapSeries(users[0], user => {
if (meirimStatus === meirimStatuses.PUBLIC_OBJECTION) Email.planDepositAlert(user, plan)
else Email.donePlanAlert(user, plan, meirimStatus)
});
})
.then(a => {
if (meirimStatus === meirimStatuses.PUBLIC_OBJECTION) plan.save({ 'status_email': 1 });
else plan.save({ 'status_email': 3 });
})
.then(plan.save({ 'was_deposited': true }))
.then(a => {return sendEmail;})
.catch(err => Log.error('Error:', err.message))
;
}
return sendEmail;
}

function shouldSendEmail(plan, planStatuses, mavatStatus) {
function shouldSendEmail(planStatuses, mavatStatus) {
var sendEmail = false;
if (!plan.attributes.was_deposited) {
for (var i = 0; i < planStatuses.length; ++i) {
const status = planStatuses[i];
if (Boolean(status.attributes.status) && mavatStatus.indexOf(status.attributes.status) >= 0) {
sendEmail = true;
}
for (var i = 0; i < planStatuses.length; ++i) {
const status = planStatuses[i];
if (Boolean(status.attributes.status) && mavatStatus.indexOf(status.attributes.status) >= 0) {
sendEmail = true;
}
}
}
return sendEmail;
}

Expand Down
4 changes: 4 additions & 0 deletions server/api/lib/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@ logger.level = 'debug';
module.exports = {
debug: (...args) => {
logger.debug(args);
console.log (args);
},
info: (...args) => {
logger.info(args);
console.log (args);
},
error: (...args) => {
logger.error(args);
console.log (args);
},
warn: (...args) => {
logger.warn(args);
console.log (args);
}
};
9 changes: 3 additions & 6 deletions server/api/model/plan_status_change.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,10 @@ class PlanStatusChange extends Model {
* @param {meirimStatus} string
* @returns array of mavat statuses
*/
static async byMeirimStatus(meirimStatus) {
static async getAllStatuses() {
const res = await Knex.raw(
`SELECT m.mavat_status
FROM status_mapping m
WHERE meirim_status=?`
, [meirimStatus]
);
`SELECT m.mavat_status, m.meirim_status
FROM status_mapping m`);
return res;
}
}
Expand Down
42 changes: 42 additions & 0 deletions server/api/service/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const Juice = require('juice');
const Log = require('../lib/log');
const Config = require('../lib/config');
const Alert = require('../model/alert');
const { meirimStatuses } = require('../constants');

class Email {
/**
Expand Down Expand Up @@ -145,6 +146,47 @@ class Email {
return this.sendWithTemplate(this.templates.planDeposit, data);
}

donePlanAlert (user, unsentPlan, meirimStatus, planStaticMap) {
const alert = new Alert({
id: user.alert_id,
person_id: user.person_id
});
const data = user;

Object.assign(data, unsentPlan.attributes);
if (data.data.DEPOSITING_DATE) {
const dates = data.data.DEPOSITING_DATE.split('T');
data.data.DEPOSITING_DATE = dates[0];
}


if (meirimStatus === meirimStatuses.APPROVED) {
data.data.PLAN_STATE_DESC = 'אושרה';
data.data.PLAN_STATE_LONG = meirimStatuses.APPROVED;
} else {
data.data.PLAN_STATE_DESC = 'בוטלה';
data.data.PLAN_STATE_LONG = meirimStatuses.CANCELLED;
}

data.unsubscribeLink =
`${this.baseUrl}alerts/unsubscribe/${alert.unsubsribeToken()}`;
data.link = `${this.baseUrl}plan/${unsentPlan.get('id')}`;
data.jurisdiction = unsentPlan.get('jurisdiction');
data.isLocalAuthority = data.jurisdiction === 'מקומית';
data.type = 'plan-alert';

data.attachments = [
{
cid: 'planmap',
filename: 'plan_map.png',
content: planStaticMap,
encoding: 'base64'
}
];

return this.sendWithTemplate(this.templates.planDone, data);
}

formatDate(date) {
const d = new Date(date);
return `${(d.getDate() > 9) ? d.getDate() : ('0' + d.getDate())}/${(d.getMonth() > 8) ? (d.getMonth() + 1) : ('0' + (d.getMonth() + 1))}/${d.getFullYear()}`;
Expand Down
70 changes: 70 additions & 0 deletions server/api/service/email/planDone.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html dir="rtl">

<head>
<title>
{{#data.PLAN_COUNTY_NAME}} תכנית {{{data.PLAN_STATE_DESC}}} ב{{{data.PLAN_COUNTY_NAME}}} {{/data.PLAN_COUNTY_NAME}} {{^data.PLAN_COUNTY_NAME}} תכנית
{{{data.PLAN_STATE_DESC}}} באזור העניין שלך {{/data.PLAN_COUNTY_NAME}} - {{{data.PL_NAME}}}</title>
</head>

<body dir="rtl">
<h2 class="headline">יש לך התראה חדשה!</h2>
{{#data.PLAN_COUNTY_NAME}}
<h2 class="headline">תכנית {{{data.PLAN_STATE_DESC}}} ב{{data.PLAN_COUNTY_NAME}}</h2>
{{/data.PLAN_COUNTY_NAME}} {{^data.PLAN_COUNTY_NAME}}
<h2 class="headline">תכנית {{{data.PLAN_STATE_DESC}}} באזור העניין שלך</h2>
{{/data.PLAN_COUNTY_NAME}}

<a href="{{{link}}}?utm_medium=email&utm_source={{{type}}}"><h2 class="headline">{{data.PL_NAME}}</h2></a>
<a href="{{{link}}}?utm_medium=email&utm_source={{{type}}}"><img src="cid:planmap" alt="Plan Map" border="0" align="center" style="width:100%; max-width: 600px; margin-bottom: 20px; height: auto; background: #dddddd;" /></a>

<div class="subSubHeadline">
<strong>סטטוס:</strong> {{data.PLAN_STATE_LONG}}
<br> {{#data.DEPOSITING_DATE}} <strong>תאריך הפקדה:</strong> {{data.DEPOSITING_DATE}} {{/data.DEPOSITING_DATE}}
</div>
<h3>
מטרת התוכנית:
</h3>
<div class="content">
{{{goals_from_mavat}}}
</div>

<h3>תיאור התוכנית:</h3>
<div class="content">
{{{main_details_from_mavat}}}
</div>
<h3>
פרטים נוספים:
</h3>
<div class="subSubHeadline">
<strong>מספר התוכנית:</strong> {{data.PL_NUMBER}}
</div>

{{#data.PLAN_COUNTY_NAME}}
<div class="subSubHeadline">
<strong>ישוב:</strong> {{data.PLAN_COUNTY_NAME}}
</div>
{{/data.PLAN_COUNTY_NAME}}
{{#jurisdiction}}
<div class="subSubHeadline">
<strong>מוסד התכנון המוסמך להפקיד את התכנית:</strong> {{jurisdiction}}
</div>
{{/jurisdiction}}
<div class="subSubHeadline">
<strong>סוג תוכנית:</strong> {{data.ENTITY_SUBTYPE_DESC}}
</div>
<div class="subSubHeadline">
<strong>
שימושי קרקע:
</strong>
{{data.PL_LANDUSE_STRING}}
</div>

<div class="link_wrapper">
<a class="link" href="{{{link}}}?utm_medium=email&utm_source={{{type}}}">צפייה במסמכי התכנית</a>
</div>
<a href="{{{unsubscribeLink}}}?utm_medium=email&utm_source={{{type}}}">מחיקת התראה</a>

</body>

</html>
3 changes: 2 additions & 1 deletion server/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@
"digestPlans": ""
}
},
"send_plan_deposit_email": true
"send_plan_deposit_email": true,
"send_plan_end_email": true
},
"uptimeRobot": {
"iplanHeartbeatUrl": "https://heartbeat.uptimerobot.com/m792921014-956ad159c92c5627a866c35252d5348617fe05a8",
Expand Down
11 changes: 11 additions & 0 deletions server/migrations/20230529_plan_status_email.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { meirimStatuses } = require('../api/constants');

exports.up = function(knex) {
return knex.raw('ALTER TABLE `plan` CHANGE COLUMN `was_deposited` `status_email` TINYINT(1) NULL DEFAULT \'0\';').then(() => {
return knex.raw('UPDATE plan set status_email=3 where id in (select distinct p.plan_id from plan_status_change p join status_mapping s on s.mavat_status = p.`status` and s.meirim_status in (\''+meirimStatuses.CANCELLED+'\',\''+meirimStatuses.APPROVED+'\'))');
});
};

exports.down = async function(knex) {
await knex.raw('ALTER TABLE `plan` CHANGE COLUMN `status_email` `was_deposited` TINYINT(1) NULL DEFAULT \'0\';');
};
7 changes: 6 additions & 1 deletion server/tests/integration/process/email.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ describe('Emails', function() {
{ mavat_status:'רישום נתוני פרסום בעיתונות על העברה להערות והשגות', meirim_status: 'התנגדויות והערות הציבור' },
{ mavat_status:'רישום נתוני פרסום ברשומות על העברה להערות והשגות', meirim_status: 'התנגדויות והערות הציבור' },
{ mavat_status:'תיקון התכנית לקראת פרסום לפי סעיף 106 ב', meirim_status: 'התנגדויות והערות הציבור' },
{ mavat_status: 'פרסום לאישור בעיתונים', meirim_status: 'תכנית מאושרת'},
{ mavat_status: 'התכנית אושרה', meirim_status: 'תכנית מאושרת'},
{ mavat_status: 'התכנית נדחתה', meirim_status: 'תכנית מבוטלת'},
{ mavat_status: 'פרסום תכנית לדחייה בעיתונים', meirim_status: 'תכנית מבוטלת'},

];
await mockDatabase.insertData(['status_mapping'], { 'status_mapping': [status_mapping1] });

Expand Down Expand Up @@ -266,7 +271,7 @@ describe('Emails', function() {
// actual reply copied from a browser performing the API response
.replyWithFile(
200,
`${__dirname}/files/new_mavat_plan_json_page.html`,
`${__dirname}/files/new_mavat_plan_json_page_deposit.html`,
{ 'Content-Type': 'text/html' }
);

Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion server/tests/tables_structs/plan_struct.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const PlanStruct = function(table) {
table.string('MP_ID', 256);
table.string('plan_new_mavat_url', 256);
table.timestamp('last_visited_status');
table.integer('was_deposited').defaultTo(0);
table.integer('status_email').defaultTo(0);
return table;
};

Expand Down