Untitled

 avatar
unknown
javascript
4 years ago
48 kB
10
Indexable
const validator = require('../helpers/validator');
const setting = require('../helpers/setting');
const constants = require('../helpers/constants');
const calculator = require('../helpers/calculator');
const googleAPIHelper = require('../helpers/googleapi');
const db = require('../models');
const md5 = require('md5');
const jwt = require('jsonwebtoken');
const axios = require('axios');
const empty = require('is-empty');
const isNumber = require('is-number');
const sequelize = require('sequelize');
const Op = sequelize.Op;

const User = db.User;
const Customer = db.Customer;
const Payment = db.Payment;
const Sales = db.Sales;
const Due = db.Due;
const Recommend = db.Recommend;
const Itinerary = db.Itinerary;
const ItineraryAllocation = db.ItineraryAllocation;

class ItineraryController {
  static fetchAll = async (req, res) => {
    if (req.auth === undefined || req.auth.id === undefined) {
      return res.status(401).send({
        message: 'JWT Token invalid',
      });
    }

    // Current month, year
    const currentDateString = new Date().toLocaleString('en-US', {
      timeZone: setting.DEFAULT_COUNTRY_TIME_ZONE,
    });
    let currentDate = new Date(currentDateString);

    let currentMonth = currentDate.getMonth() + 1;
    let currentYear = currentDate.getFullYear();
    let currentDay = currentDate.getDate();

    const user = await User.findOne({
      where: {
        id: req.auth.id,
      },
    });

    if (user === null) {
      return res.status(401).send({
        message: 'User does not exist',
      });
    }

    if (user.role !== 'M' && user.role !== 'C') {
      return res.status(401).send({
        message: 'Role does not have enough permission',
      });
    }

    const users = await User.findAll({
      where: {
        role: 'F',
      },
    });

    const customers = await Customer.findAll({ attributes: ['zipcode', 'city'], group: 'zipcode' });

    let zipCities = {};

    for (let i = 0; i < customers.length; i++) {
      const zipcode = Number(customers[i].zipcode);
      const city = customers[i].city;
      if (!empty(zipcode) && !isNaN(zipcode) && isNumber(zipcode) && !(zipcode in zipCities)) {
        zipCities[zipcode] = city;
      }
    }

    for (const user of users) {
      const token = this.generateAccessToken(user);

      // * call /month-summary API
      await axios({
        method: 'GET',
        url: constants.TVS_API_URL + '/v1/month-summary',
        headers: {
          'Content-Type': 'application/json',
          Authorization: 'Bearer ' + token,
        },
      })
        .then((response) => {
          if (response.status == 200) {
            return response.data;
          }
        })
        .then(async (data) => {
          let itinerary = null;
          let recal_ptp_visit_pending = 0;
          let recal_mandatory_visit_pending = 0;
          let recal_regular_visit_pending = 0;

          let ptp_visit_total_recal = 0;
          let mandatory_visit_total_recal = 0;
          let regular_visit_total_recal = 0;

          await Itinerary.findOrCreate({
            where: {
              month: currentMonth,
              year: currentYear,
              user_id: user.id,
              agent_id: user.agent_id,
              client_code: user.client_code,
            },
            defaults: {
              month: currentMonth,
              year: currentYear,
              user_id: user.id,
              agent_id: user.agent_id,
              client_code: user.client_code,
              is_ai: user.is_ai,
              cs_name: user.first_name + ' ' + user.last_name,
              ptp_visit_pending: 0,
              mandatory_visit_pending: 0,
              regular_visit_pending: 0,
            },
          })
            .then(function (result) {
              itinerary = result[0];
            })
            .catch((err) => {
              console.log('Error occurred while get itinerary ' + err.message);
            });

          for (const cluster of data.clusters) {
            const lat = cluster.cluster_coordinates[0];
            const lng = cluster.cluster_coordinates[1];

            if (empty(lat) || empty(lng)) {
              continue;
            }

            let itineraryAlloc = null;

            recal_ptp_visit_pending = recal_ptp_visit_pending + cluster.ptp_visit;
            recal_mandatory_visit_pending = recal_mandatory_visit_pending + cluster.mandatory_visit;
            recal_regular_visit_pending = recal_regular_visit_pending + cluster.regular_visit;

            await ItineraryAllocation.findOrCreate({
              where: {
                month: currentMonth,
                year: currentYear,
                itinerary_id: itinerary.id,
                user_id: user.id,
                agent_id: user.agent_id,
                client_code: user.client_code,
                cs_name: itinerary.cs_name,
                zipcode: cluster.zipcode,
              },
              defaults: {
                month: currentMonth,
                year: currentYear,
                itinerary_id: itinerary.id,
                user_id: user.id,
                agent_id: user.agent_id,
                client_code: user.client_code,
                cs_name: itinerary.cs_name,
                zipcode: cluster.zipcode,
                lat: lat,
                lng: lng,
                ptp_visit_pending: cluster.ptp_visit,
                ptp_visit_total: cluster.ptp_visit,
                ptp_visit_completed: 0,
                ptp_visit_percent: '0',
                mandatory_visit_pending: cluster.mandatory_visit,
                mandatory_visit_total: cluster.mandatory_visit,
                mandatory_visit_completed: 0,
                mandatory_visit_percent: '0',
                regular_visit_pending: cluster.regular_visit,
                regular_visit_total: cluster.regular_visit,
                regular_visit_completed: 0,
                regular_visit_percent: '0',
              },
            })
              .then(async function (result) {
                itineraryAlloc = result[0];
                let created = result[1];

                if (created) {
                  // * total
                  ptp_visit_total_recal = ptp_visit_total_recal + cluster.ptp_visit;
                  mandatory_visit_total_recal = mandatory_visit_total_recal + cluster.mandatory_visit;
                  regular_visit_total_recal = regular_visit_total_recal + cluster.regular_visit;

                  itinerary.ptp_visit_total = itinerary.ptp_visit_total + cluster.ptp_visit;
                  itinerary.mandatory_visit_total = itinerary.mandatory_visit_total + cluster.mandatory_visit;
                  itinerary.regular_visit_total = itinerary.regular_visit_total + cluster.regular_visit;

                  await itinerary.save();
                }
              })
              .catch((err) => {
                console.log('Error occurred while get itinerary allocation' + err.message);
              });

            // * use Google API
            await axios({
              method: 'GET',
              url:
                'https://maps.googleapis.com/maps/api/geocode/json?latlng=' +
                lat +
                ',' +
                lng +
                '&sensor=true&key=' +
                constants.GOOGLE_GEOCODING_API_KEY,
              headers: {
                'Content-Type': 'application/json',
              },
            })
              .then(async (response) => {
                if (response.status == 200) {
                  let geoData = googleAPIHelper.getAddressParts(response.data.results[0]);
                  if (empty(geoData.locality)) {
                    geoData = googleAPIHelper.getAddressParts(response.data.results[1]);
                  }

                  if (!empty(geoData.locality)) {
                    itineraryAlloc.city = geoData.locality;
                  } else {
                    // ! still cannot find city...
                    itineraryAlloc.city = zipCities[itineraryAlloc.zipcode];
                  }

                  await itineraryAlloc.save();

                  return;
                }
              })
              .catch((err) => {
                console.log('Error occurred while get geo data ' + err.message);
              });
          }

          itinerary.ptp_visit_pending = recal_ptp_visit_pending;
          itinerary.mandatory_visit_pending = recal_mandatory_visit_pending;
          itinerary.regular_visit_pending = recal_regular_visit_pending;

          await itinerary.save();
        })
        .catch(function (error) {
          console.log(error);
        });
    }

    return res.status(204).send({});
  };

  static fetch = async (req, res) => {
    if (req.auth === undefined || req.auth.id === undefined) {
      return res.status(401).send({
        message: 'JWT Token invalid',
      });
    }

    // Current month, year
    const currentDateString = new Date().toLocaleString('en-US', {
      timeZone: setting.DEFAULT_COUNTRY_TIME_ZONE,
    });
    const currentDate = new Date(currentDateString);

    const currentMonth = currentDate.getMonth() + 1;
    const currentYear = currentDate.getFullYear();
    const currentDay = currentDate.getDate();

    const de = new Date(currentDate.getFullYear(), currentMonth - 2, currentDay);
    const previousYear = de.getFullYear();
    const previousMonth = de.getMonth() + 1;

    const user = await User.findOne({
      where: {
        id: req.auth.id,
      },
    });

    const customers = await Customer.findAll({ attributes: ['zipcode', 'city'], group: 'zipcode' });

    let zipCities = {};

    for (let i = 0; i < customers.length; i++) {
      const zipcode = Number(customers[i].zipcode);
      const city = customers[i].city;
      if (!empty(zipcode) && !isNaN(zipcode) && isNumber(zipcode) && !(zipcode in zipCities)) {
        zipCities[zipcode] = city;
      }
    }

    const token = this.generateAccessToken(user);

    // * call /month-summary API
    await axios({
      method: 'GET',
      url: constants.TVS_API_URL + '/v1/month-summary',
      headers: {
        'Content-Type': 'application/json',
        Authorization: 'Bearer ' + token,
      },
    })
      .then((response) => {
        if (response.status == 200) {
          return response.data;
        }
      })
      .then(async (data) => {
        let itinerary = null;

        // * get recommend
        console.log('==== recommends');
        const recommends = await Recommend.findAll({
          attributes: ['id'],
          where: {
            [Op.and]: [
              sequelize.where(sequelize.col('year'), currentYear),
              sequelize.where(sequelize.col('month'), currentMonth),
            ],
            client_code: user.client_code,
          },
          include: [
            {
              model: Sales,
              attributes: ['id'],
              as: 'sales',
              required: false,
              include: [
                {
                  model: Due,
                  attributes: ['id', 'expected_amt'],
                  as: 'dues',
                  required: false,
                  where: {
                    month_due_date: {
                      [Op.and]: [
                        sequelize.where(sequelize.fn('MONTH', sequelize.col('month_due_date')), currentMonth),
                        sequelize.where(sequelize.fn('YEAR', sequelize.col('month_due_date')), currentYear),
                      ],
                    },
                  },
                  limit: 1,
                },
              ],
            },
          ],
        });

        // * get list payment
        console.log('==== all_current_not_visits');
        const all_current_not_visits = await Payment.findAll({
          raw: true,
          attributes: ['rec_amt', 'acct_number', 'payment_note'],
          where: {
            [Op.and]: [
              sequelize.where(sequelize.col('createdBy'), 'API'),
              sequelize.where(
                sequelize.fn(
                  'YEAR',
                  sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
                ),
                currentYear,
              ),
              sequelize.where(
                sequelize.fn(
                  'MONTH',
                  sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
                ),
                currentMonth,
              ),
              !validator.isLastDayOfMonth(currentDay)
                ? sequelize.where(
                    sequelize.fn(
                      'DAY',
                      sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
                    ),
                    {
                      [Op.lte]: currentDay,
                    },
                  )
                : '',
            ],
            payment_note: {
              [Op.in]: ['Skip Visit'],
            },
            client_code: user.client_code,
          },
        });

        console.log('==== all_current_with_not_visit_payments');
        const all_current_with_not_visit_payments = await Payment.findAll({
          raw: true,
          attributes: ['rec_amt', 'acct_number', 'payment_note', 'visit_type'],
          where: {
            [Op.and]: [
              sequelize.where(sequelize.col('createdBy'), 'API'),
              sequelize.where(
                sequelize.fn(
                  'YEAR',
                  sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
                ),
                currentYear,
              ),
              sequelize.where(
                sequelize.fn(
                  'MONTH',
                  sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
                ),
                currentMonth,
              ),
              !validator.isLastDayOfMonth(currentDay)
                ? sequelize.where(
                    sequelize.fn(
                      'DAY',
                      sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
                    ),
                    {
                      [Op.lte]: currentDay,
                    },
                  )
                : '',
            ],
            client_code: user.client_code,
          },
        });

        // * calculate recovery rate
        let completed_visits = 0;
        let expected_visits = 0;
        let collected_amount = 0;
        let expected_amount = 0;

        for (const p of all_current_with_not_visit_payments) {
          var actual_collection = parseFloat(p['rec_amt']);
          if (!empty(actual_collection) && p.payment_note != 'Skip Visit') {
            completed_visits++;
            collected_amount = collected_amount + actual_collection;
          }
        }

        for (const recommend of recommends) {
          const expectedl_collection = parseFloat(recommend.sales.dues[0].expected_amt);
          expected_visits++;
          expected_amount = expected_amount + expectedl_collection;
        }

        await Itinerary.findOrCreate({
          where: {
            month: currentMonth,
            year: currentYear,
            user_id: user.id,
            agent_id: user.agent_id,
            client_code: user.client_code,
          },
          defaults: {
            month: currentMonth,
            year: currentYear,
            user_id: user.id,
            agent_id: user.agent_id,
            client_code: user.client_code,
            is_ai: user.is_ai,
            cs_name: user.first_name + ' ' + user.last_name,
            itinerary_incompliant: 0.0,
            itinerary_incompliant_status: 'nil',
            skipped_visits: 0,
            collection_efficiency: 0.0,
            recovery_rate: 0.0,
            ptp_visit_pending: 0,
            ptp_visit_completed: 0,
            ptp_visit_percent: 0.0,
            mandatory_visit_pending: 0,
            mandatory_visit_completed: 0,
            mandatory_visit_percent: 0.0,
            regular_visit_pending: 0,
            regular_visit_completed: 0,
            regular_visit_percent: 0.0,
          },
          include: [
            {
              model: ItineraryAllocation,
              as: 'itinerary_allocations',
              require: false,
            },
          ],
        })
          .then(function (result) {
            itinerary = result[0];
          })
          .catch((err) => {
            console.log('Error occurred while get itinerary ' + err.message);
          });

        for (const cluster of data.clusters) {
          const lat = cluster.cluster_coordinates[0];
          const lng = cluster.cluster_coordinates[1];

          if (empty(lat) || empty(lng)) {
            continue;
          }

          let itineraryAlloc = null;

          await ItineraryAllocation.findOrCreate({
            where: {
              month: currentMonth,
              year: currentYear,
              itinerary_id: itinerary.id,
              user_id: user.id,
              agent_id: user.agent_id,
              client_code: user.client_code,
              zipcode: cluster.zipcode,
            },
            defaults: {
              month: currentMonth,
              year: currentYear,
              itinerary_id: itinerary.id,
              user_id: user.id,
              agent_id: user.agent_id,
              client_code: user.client_code,
              cs_name: itinerary.cs_name,
              zipcode: cluster.zipcode,
              lat: lat,
              lng: lng,
              ptp_visit_pending: cluster.ptp_visit,
              ptp_visit_total: cluster.ptp_visit,
              ptp_visit_completed: 0,
              ptp_visit_percent: 0.0,
              mandatory_visit_pending: cluster.mandatory_visit,
              mandatory_visit_total: cluster.mandatory_visit,
              mandatory_visit_completed: 0,
              mandatory_visit_percent: 0.0,
              regular_visit_pending: cluster.regular_visit,
              regular_visit_total: cluster.regular_visit,
              regular_visit_completed: 0,
              regular_visit_percent: 0.0,
            },
          })
            .then(async function (result) {
              itineraryAlloc = result[0];
              let created = result[1];

              if (created) {
                // * call Google API
                await axios({
                  method: 'GET',
                  url:
                    'https://maps.googleapis.com/maps/api/geocode/json?latlng=' +
                    lat +
                    ',' +
                    lng +
                    '&sensor=true&key=' +
                    constants.GOOGLE_GEOCODING_API_KEY,
                  headers: {
                    'Content-Type': 'application/json',
                  },
                })
                  .then(async (response) => {
                    if (response.status == 200) {
                      let geoData = googleAPIHelper.getAddressParts(response.data.results[0]);
                      if (empty(geoData.locality)) {
                        geoData = googleAPIHelper.getAddressParts(response.data.results[1]);
                      }

                      if (!empty(geoData.locality)) {
                        itineraryAlloc.city = geoData.locality;
                      } else {
                        // ! still cannot find city...
                        itineraryAlloc.city = zipCities[itineraryAlloc.zipcode];
                      }
                      return;
                    }
                  })
                  .catch((err) => {
                    console.log('Error occurred while get geo data ' + err.message);
                  });
              }

              itineraryAlloc.ptp_visit_pending = cluster.ptp_visit;
              itineraryAlloc.mandatory_visit_pending = cluster.mandatory_visit;
              itineraryAlloc.regular_visit_pending = cluster.regular_visit;

              itineraryAlloc.ptp_visit_total = itineraryAlloc.ptp_visit_pending + itineraryAlloc.ptp_visit_completed;
              itineraryAlloc.mandatory_visit_total =
                itineraryAlloc.mandatory_visit_pending + itineraryAlloc.mandatory_visit_completed;
              itineraryAlloc.regular_visit_total =
                itineraryAlloc.regular_visit_pending + itineraryAlloc.regular_visit_completed;

              // * calculate % completed
              if (itineraryAlloc.ptp_visit_pending == 0 && itineraryAlloc.ptp_visit_completed != 0) {
                itineraryAlloc.ptp_visit_percent = 100.0;
              } else {
                itineraryAlloc.ptp_visit_percent =
                  itineraryAlloc.ptp_visit_completed != 0 && itineraryAlloc.ptp_visit_total != 0
                    ? ((itineraryAlloc.ptp_visit_completed / itineraryAlloc.ptp_visit_total) * 100.0).toFixed(1)
                    : 0.0;
              }

              await itineraryAlloc.save();
            })
            .catch((err) => {
              console.log('Error occurred while get itinerary allocation' + err.message);
            });
        }

        await itinerary.reload();

        let recal_ptp_visit_total = 0;
        let recal_mandatory_visit_total = 0;
        let recal_regular_visit_total = 0;

        let recal_ptp_visit_pending = 0;
        let recal_mandatory_visit_pending = 0;
        let recal_regular_visit_pending = 0;

        for (const ia of itinerary.itinerary_allocations) {
          recal_ptp_visit_total = recal_ptp_visit_total + ia.ptp_visit_total;
          recal_mandatory_visit_total = recal_mandatory_visit_total + ia.mandatory_visit_total;
          recal_regular_visit_total = recal_regular_visit_total + ia.regular_visit_total;

          recal_ptp_visit_pending = recal_ptp_visit_pending + ia.ptp_visit_pending;
          recal_mandatory_visit_pending = recal_mandatory_visit_pending + ia.mandatory_visit_pending;
          recal_regular_visit_pending = recal_regular_visit_pending + ia.regular_visit_pending;
        }

        itinerary.ptp_visit_total = recal_ptp_visit_total;
        itinerary.mandatory_visit_total = recal_mandatory_visit_total;
        itinerary.regular_visit_total = recal_regular_visit_total;

        itinerary.ptp_visit_pending = recal_ptp_visit_pending;
        itinerary.mandatory_visit_pending = recal_mandatory_visit_pending;
        itinerary.regular_visit_pending = recal_regular_visit_pending;

        // * calculate % completed
        if (itinerary.ptp_visit_pending == 0 && itinerary.ptp_visit_completed != 0) {
          itinerary.ptp_visit_percent = 100.0;
        } else {
          itinerary.ptp_visit_percent =
            itinerary.ptp_visit_completed != 0 && itinerary.ptp_visit_total != 0
              ? ((itinerary.ptp_visit_completed / itinerary.ptp_visit_total) * 100.0).toFixed(1)
              : 0.0;
        }

        // * get previous itinerary
        const prev_itinerary = await Itinerary.findOne({
          where: {
            user_id: user.id,
            month: previousMonth,
            year: previousYear,
          },
        });

        // * calculate Itinerary Non-compliance
        const current_itinerary_non_compliance =
          all_current_not_visits.length != 0 && all_current_with_not_visit_payments.length != 0
            ? (all_current_not_visits.length / all_current_with_not_visit_payments.length) * 100
            : 0;
        const prev_itinerary_non_compliance = !empty(prev_itinerary) ? prev_itinerary.itinerary_incompliant : 0;

        // * recalculate collection efficiency, recovery rate, skipped visits
        itinerary.collection_efficiency = ((data.collected_amount / data.expected_amount) * 100.0).toFixed(1);
        if (!empty(completed_visits) && !empty(expected_visits)) {
          itinerary.recovery_rate = ((completed_visits / expected_visits) * 100.0).toFixed(1);
        }

        itinerary.itinerary_incompliant = current_itinerary_non_compliance.toFixed(1);
        itinerary.itinerary_incompliant_status = calculator.calculatePercentStatus(
          prev_itinerary_non_compliance,
          current_itinerary_non_compliance,
        );

        itinerary.skipped_visits = all_current_not_visits.length;

        await itinerary.save();
      })
      .catch(function (error) {
        console.log(error);
      });

    return res.status(204).send({});
  };

  static recalculateItinerary = async (payment, user, customer) => {
    // Current month, year
    const currentDateString = new Date().toLocaleString('en-US', {
      timeZone: setting.DEFAULT_COUNTRY_TIME_ZONE,
    });
    const currentDate = new Date(currentDateString);
    const currentMonth = currentDate.getMonth() + 1; // 0=January
    const currentYear = currentDate.getFullYear(); // 2021
    const currentDay = currentDate.getDate();

    const de = new Date(currentDate.getFullYear(), currentMonth - 2, currentDay);
    const previousYear = de.getFullYear();
    const previousMonth = de.getMonth() + 1;

    // * get itinerary allocation
    const itineraryAlloc = await ItineraryAllocation.findOne({
      where: {
        user_id: user.id,
        agent_id: user.agent_id,
        zipcode: Number(customer.zipcode),
        month: currentMonth,
        year: currentYear,
      },
    });

    // * calculate completed & total
    switch (payment.visit_type) {
      case 'regular':
        itineraryAlloc.regular_visit_pending = itineraryAlloc.regular_visit_pending - 1;

        if (payment.payment_note != 'Skip Visit') {
          itineraryAlloc.regular_visit_completed = itineraryAlloc.regular_visit_completed + 1;
        }

        itineraryAlloc.regular_visit_total =
          itineraryAlloc.regular_visit_pending + itineraryAlloc.regular_visit_completed;
        break;
      case 'mandatory':
        itineraryAlloc.mandatory_visit_pending = itineraryAlloc.mandatory_visit_pending - 1;

        if (payment.payment_note != 'Skip Visit') {
          itineraryAlloc.mandatory_visit_completed = itineraryAlloc.mandatory_visit_completed + 1;
        }

        itineraryAlloc.mandatory_visit_total =
          itineraryAlloc.mandatory_visit_pending + itineraryAlloc.mandatory_visit_completed;
        break;
      case 'ptp':
        itineraryAlloc.ptp_visit_pending = itineraryAlloc.ptp_visit_pending - 1;

        if (payment.payment_note != 'Skip Visit') {
          itineraryAlloc.ptp_visit_completed = itineraryAlloc.ptp_visit_completed + 1;
        }

        if (this.isIncreasedPTP(payment)) {
          itineraryAlloc.ptp_visit_pending = itineraryAlloc.ptp_visit_pending + 1;
          itinerary.ptp_visit_pending = itinerary.ptp_visit_pending + 1;
        }

        itineraryAlloc.ptp_visit_total = itineraryAlloc.ptp_visit_pending + itineraryAlloc.ptp_visit_completed;
        break;
    }

    // * calculate % completed itinerary allocation
    if (itineraryAlloc.ptp_visit_pending == 0 && itineraryAlloc.ptp_visit_completed != 0) {
      itineraryAlloc.ptp_visit_percent = 100.0;
    } else {
      itineraryAlloc.ptp_visit_percent =
        itineraryAlloc.ptp_visit_completed != 0 && itineraryAlloc.ptp_visit_total != 0
          ? ((itineraryAlloc.ptp_visit_completed / itineraryAlloc.ptp_visit_total) * 100.0).toFixed(1)
          : 0.0;
    }

    await itineraryAlloc.save();

    const token = this.generateAccessToken(user);

    // * call /month-summary API
    await axios({
      method: 'GET',
      url: constants.TVS_API_URL + '/v1/month-summary',
      headers: {
        'Content-Type': 'application/json',
        Authorization: 'Bearer ' + token,
      },
    })
      .then((response) => {
        if (response.status == 200) {
          return response.data;
        }
      })
      .then(async (data) => {
        for (const cluster of data.clusters) {
          await ItineraryAllocation.findOne({
            where: {
              month: currentMonth,
              year: currentYear,
              user_id: user.id,
              agent_id: user.agent_id,
              client_code: user.client_code,
              zipcode: cluster.zipcode,
            },
          })
            .then(async function (result) {
              itineraryAlloc = result;

              itineraryAlloc.ptp_visit_pending = cluster.ptp_visit;
              itineraryAlloc.mandatory_visit_pending = cluster.mandatory_visit;
              itineraryAlloc.regular_visit_pending = cluster.regular_visit;

              itineraryAlloc.ptp_visit_total = itineraryAlloc.ptp_visit_pending + itineraryAlloc.ptp_visit_completed;
              itineraryAlloc.mandatory_visit_total =
                itineraryAlloc.mandatory_visit_pending + itineraryAlloc.mandatory_visit_completed;
              itineraryAlloc.regular_visit_total =
                itineraryAlloc.regular_visit_pending + itineraryAlloc.regular_visit_completed;

              // * calculate % completed
              if (itineraryAlloc.ptp_visit_pending == 0 && itineraryAlloc.ptp_visit_completed != 0) {
                itineraryAlloc.ptp_visit_percent = 100.0;
              } else {
                itineraryAlloc.ptp_visit_percent =
                  itineraryAlloc.ptp_visit_completed != 0 && itineraryAlloc.ptp_visit_total != 0
                    ? ((itineraryAlloc.ptp_visit_completed / itineraryAlloc.ptp_visit_total) * 100.0).toFixed(1)
                    : 0.0;
              }

              await itineraryAlloc.save();
            })
            .catch((err) => {
              console.log('Error occurred while get itinerary allocation' + err.message);
            });
        }
      })
      .catch(function (error) {
        console.log(error);
      });

    // * get itinerary
    const itinerary = await Itinerary.findOne({
      include: [
        {
          model: ItineraryAllocation,
          as: 'itinerary_allocations',
          require: false,
        },
      ],
      where: {
        user_id: user.id,
        month: currentMonth,
        year: currentYear,
      },
    });

    // * get previous itinerary
    const prev_itinerary = await Itinerary.findOne({
      where: {
        user_id: user.id,
        month: previousMonth,
        year: previousYear,
      },
    });

    let recal_ptp_visit_total = 0;
    let recal_mandatory_visit_total = 0;
    let recal_regular_visit_total = 0;

    let recal_ptp_visit_completed = 0;
    let recal_mandatory_visit_completed = 0;
    let recal_regular_visit_completed = 0;

    let recal_ptp_visit_pending = 0;
    let recal_mandatory_visit_pending = 0;
    let recal_regular_visit_pending = 0;

    for (const ia of itinerary.itinerary_allocations) {
      recal_ptp_visit_total = recal_ptp_visit_total + ia.ptp_visit_total;
      recal_mandatory_visit_total = recal_mandatory_visit_total + ia.mandatory_visit_total;
      recal_regular_visit_total = recal_regular_visit_total + ia.regular_visit_total;

      recal_ptp_visit_completed = recal_ptp_visit_completed + ia.ptp_visit_completed;
      recal_mandatory_visit_completed = recal_mandatory_visit_completed + ia.mandatory_visit_completed;
      recal_regular_visit_completed = recal_regular_visit_completed + ia.regular_visit_completed;

      recal_ptp_visit_pending = recal_ptp_visit_pending + ia.ptp_visit_pending;
      recal_mandatory_visit_pending = recal_mandatory_visit_pending + ia.mandatory_visit_pending;
      recal_regular_visit_pending = recal_regular_visit_pending + ia.regular_visit_pending;
    }

    itinerary.ptp_visit_total = recal_ptp_visit_total;
    itinerary.mandatory_visit_total = recal_mandatory_visit_total;
    itinerary.regular_visit_total = recal_regular_visit_total;

    itinerary.ptp_visit_completed = recal_ptp_visit_completed;
    itinerary.mandatory_visit_completed = recal_mandatory_visit_completed;
    itinerary.regular_visit_completed = recal_regular_visit_completed;

    itinerary.ptp_visit_pending = recal_ptp_visit_pending;
    itinerary.mandatory_visit_pending = recal_mandatory_visit_pending;
    itinerary.regular_visit_pending = recal_regular_visit_pending;

    // * calculate % completed itinerary
    if (itinerary.ptp_visit_pending == 0 && itinerary.ptp_visit_completed != 0) {
      itinerary.ptp_visit_percent = 100.0;
    } else {
      itinerary.ptp_visit_percent =
        itinerary.ptp_visit_completed != 0 && itinerary.ptp_visit_total != 0
          ? ((itinerary.ptp_visit_completed / itinerary.ptp_visit_total) * 100.0).toFixed(1)
          : 0.0;
    }

    // TODO: recalculate collection efficiency, recovery rate, skipped visits
    // * get recommend
    console.log('==== recommends');
    const recommends = await Recommend.findAll({
      attributes: ['id'],
      where: {
        [Op.and]: [
          sequelize.where(sequelize.col('year'), currentYear),
          sequelize.where(sequelize.col('month'), currentMonth),
        ],
        client_code: user.client_code,
      },
      include: [
        {
          model: Sales,
          attributes: ['id'],
          as: 'sales',
          required: false,
          include: [
            {
              model: Due,
              attributes: ['id', 'expected_amt'],
              as: 'dues',
              required: false,
              where: {
                month_due_date: {
                  [Op.and]: [
                    sequelize.where(sequelize.fn('MONTH', sequelize.col('month_due_date')), currentMonth),
                    sequelize.where(sequelize.fn('YEAR', sequelize.col('month_due_date')), currentYear),
                  ],
                },
              },
              limit: 1,
            },
          ],
        },
      ],
    });

    // * get list payment
    console.log('==== all_current_not_visits');
    const all_current_not_visits = await Payment.findAll({
      raw: true,
      attributes: ['rec_amt', 'acct_number', 'payment_note'],
      where: {
        [Op.and]: [
          sequelize.where(sequelize.col('createdBy'), 'API'),
          sequelize.where(
            sequelize.fn(
              'YEAR',
              sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
            ),
            currentYear,
          ),
          sequelize.where(
            sequelize.fn(
              'MONTH',
              sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
            ),
            currentMonth,
          ),
          !validator.isLastDayOfMonth(currentDay)
            ? sequelize.where(
                sequelize.fn(
                  'DAY',
                  sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
                ),
                {
                  [Op.lte]: currentDay,
                },
              )
            : '',
        ],
        payment_note: {
          [Op.in]: ['Skip Visit'],
        },
        client_code: user.client_code,
      },
    });

    console.log('==== all_current_with_not_visit_payments');
    const all_current_with_not_visit_payments = await Payment.findAll({
      raw: true,
      attributes: ['rec_amt', 'acct_number', 'payment_note', 'visit_type'],
      where: {
        [Op.and]: [
          sequelize.where(sequelize.col('createdBy'), 'API'),
          sequelize.where(
            sequelize.fn(
              'YEAR',
              sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
            ),
            currentYear,
          ),
          sequelize.where(
            sequelize.fn(
              'MONTH',
              sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
            ),
            currentMonth,
          ),
          !validator.isLastDayOfMonth(currentDay)
            ? sequelize.where(
                sequelize.fn(
                  'DAY',
                  sequelize.fn('CONVERT_TZ', sequelize.col('rec_date'), 'UTC', setting.DEFAULT_COUNTRY_TIME_ZONE),
                ),
                {
                  [Op.lte]: currentDay,
                },
              )
            : '',
        ],
        client_code: user.client_code,
      },
    });

    // * calculate recovery rate
    let completed_visits = 0;
    let expected_visits = 0;
    let collected_amount = 0;
    let expected_amount = 0;

    for (const p of all_current_with_not_visit_payments) {
      var actual_collection = parseFloat(p['rec_amt']);
      if (!empty(actual_collection) && p.payment_note != 'Skip Visit') {
        completed_visits++;
        collected_amount = collected_amount + actual_collection;
      }
    }

    for (const recommend of recommends) {
      const expectedl_collection = parseFloat(recommend.sales.dues[0].expected_amt);
      expected_visits++;
      expected_amount = expected_amount + expectedl_collection;
    }

    // * calculate Itinerary Non-compliance
    const current_itinerary_non_compliance =
      all_current_not_visits.length != 0 && all_current_with_not_visit_payments.length != 0
        ? (all_current_not_visits.length / all_current_with_not_visit_payments.length) * 100
        : 0;
    const prev_itinerary_non_compliance = !empty(prev_itinerary) ? prev_itinerary.itinerary_incompliant : 0;

    // * recalculate collection efficiency, recovery rate, skipped visits
    itinerary.collection_efficiency = ((collected_amount / expected_amount) * 100.0).toFixed(1);
    if (!empty(completed_visits) && !empty(expected_visits)) {
      itinerary.recovery_rate = ((completed_visits / expected_visits) * 100.0).toFixed(1);
    }

    itinerary.itinerary_incompliant = current_itinerary_non_compliance.toFixed(1);
    itinerary.itinerary_incompliant_status = calculator.calculatePercentStatus(
      prev_itinerary_non_compliance,
      current_itinerary_non_compliance,
    );

    itinerary.skipped_visits = all_current_not_visits.length;

    await itinerary.save();

    return;
  };

  static listItinerary = async (req, res) => {
    if (req.auth === undefined || req.auth.id === undefined) {
      return res.status(401).send({
        message: 'JWT Token invalid',
      });
    }

    // Current month, year
    const currentDateString = new Date().toLocaleString('en-US', {
      timeZone: setting.DEFAULT_COUNTRY_TIME_ZONE,
    });
    let currentDate = new Date(currentDateString);

    let currentMonth = currentDate.getMonth() + 1;
    let currentYear = currentDate.getFullYear();

    const user = await User.findOne({
      where: {
        id: req.auth.id,
      },
    });

    if (user === null) {
      return res.status(401).send({
        message: 'User does not exist',
      });
    }

    if (user.role !== 'M' && user.role !== 'C') {
      return res.status(401).send({
        message: 'Role does not have enough permission',
      });
    }

    // TODO: Grab the list agents by branch
    let client_codes = [];
    const list_agents_by_branch = await User.findAll({
      raw: true,
      attributes: ['client_code', 'agent_id'],
      where: {
        [Op.and]: [
          sequelize.where(sequelize.col('role'), 'F'),
          validator.isBranchManager(req.auth) ? sequelize.where(sequelize.col('manager_id'), user.id) : '',
        ],
      },
    });

    for (const l of list_agents_by_branch) {
      client_codes.push(l['client_code']);
    }

    return Itinerary.findAll({
      include: [
        {
          model: ItineraryAllocation,
          as: 'itinerary_allocations',
          require: false,
        },
      ],
      where: {
        month: currentMonth,
        year: currentYear,
        [Op.and]: [
          validator.isBranchManager(req.auth)
            ? sequelize.where(sequelize.col('client_code'), {
                [Op.in]: client_codes,
              })
            : '',
        ],
      },
      order: sequelize.literal('collection_efficiency DESC'),
    })
      .then((itineraries) => res.status(200).send({ data: itineraries }))
      .catch((error) => res.status(400).send(error));
  };

  static viewItinerary = async (req, res) => {
    if (req.auth === undefined || req.auth.id === undefined) {
      return res.status(401).send({
        message: 'JWT Token invalid',
      });
    }

    const user = await User.findOne({
      where: {
        id: req.auth.id,
      },
    });

    if (user === null) {
      return res.status(401).send({
        message: 'User does not exist',
      });
    }

    if (user.role !== 'M' && user.role !== 'C') {
      return res.status(401).send({
        message: 'Role does not have enough permission',
      });
    }

    let itineraryID = req.params.id;

    if (empty(itineraryID)) {
      return res.status(400).send({
        message: 'Bad Request',
      });
    }

    return Itinerary.findAll({
      include: [
        {
          model: ItineraryAllocation,
          as: 'itinerary_allocations',
          require: false,
        },
      ],
      where: {
        id: itineraryID,
      },
    })
      .then((itinerary) => res.status(200).send({ data: itinerary }))
      .catch((error) => res.status(400).send(error));
  };

  static listItineraryAllocation = async (req, res) => {
    if (req.auth === undefined || req.auth.id === undefined) {
      return res.status(401).send({
        message: 'JWT Token invalid',
      });
    }

    // Current month, year
    const currentDateString = new Date().toLocaleString('en-US', {
      timeZone: setting.DEFAULT_COUNTRY_TIME_ZONE,
    });
    let currentDate = new Date(currentDateString);

    let currentMonth = currentDate.getMonth() + 1;
    let currentYear = currentDate.getFullYear();

    const user = await User.findOne({
      where: {
        id: req.auth.id,
      },
    });

    if (user === null) {
      return res.status(401).send({
        message: 'User does not exist',
      });
    }

    if (user.role !== 'M' && user.role !== 'C') {
      return res.status(401).send({
        message: 'Role does not have enough permission',
      });
    }

    // TODO: Grab the list agents by branch
    let client_codes = [];
    const list_agents_by_branch = await User.findAll({
      raw: true,
      attributes: ['client_code', 'agent_id'],
      where: {
        [Op.and]: [
          sequelize.where(sequelize.col('role'), 'F'),
          validator.isBranchManager(req.auth) ? sequelize.where(sequelize.col('manager_id'), user.id) : '',
        ],
      },
    });

    for (const l of list_agents_by_branch) {
      client_codes.push(l['client_code']);
    }

    return ItineraryAllocation.findAll({
      include: [
        {
          model: Itinerary,
          as: 'itinerary',
          attributes: ['id', 'is_ai'],
        },
      ],
      where: {
        month: currentMonth,
        year: currentYear,
        [Op.and]: [
          validator.isBranchManager(req.auth)
            ? sequelize.where(sequelize.col('ItineraryAllocation.client_code'), {
                [Op.in]: client_codes,
              })
            : '',
        ],
      },
      order: sequelize.literal('city ASC'),
    })
      .then((itineraryAllocations) => res.status(200).send({ data: itineraryAllocations }))
      .catch((error) => res.status(400).send(error));
  };

  static viewItineraryAllocation = async (req, res) => {
    if (req.auth === undefined || req.auth.id === undefined) {
      return res.status(401).send({
        message: 'JWT Token invalid',
      });
    }

    const user = await User.findOne({
      where: {
        id: req.auth.id,
      },
    });

    if (user === null) {
      return res.status(401).send({
        message: 'User does not exist',
      });
    }

    if (user.role !== 'M' && user.role !== 'C') {
      return res.status(401).send({
        message: 'Role does not have enough permission',
      });
    }

    let itineraryID = req.params.id;

    if (empty(itineraryID)) {
      return res.status(400).send({
        message: 'Bad Request',
      });
    }

    return ItineraryAllocation.findAll({
      include: [
        {
          model: Itinerary,
          as: 'itinerary',
          attributes: ['id', 'is_ai'],
        },
      ],
      where: {
        itinerary_id: itineraryID,
      },
    })
      .then((itineraryAllocation) => res.status(200).send({ data: itineraryAllocation }))
      .catch((error) => res.status(400).send(error));
  };

  static generateAccessToken = (user) => {
    let token = jwt.sign(
      {
        id: user.id,
        role: user.role,
        phone_number: user.phone_number,
        country_code: user.country_code,
        email: user.email,
      },
      constants.JWT_SECRET,
      {
        expiresIn: 900, // in second
      },
    );
    return token;
  };

  static isIncreasedTotal = (payment) => {
    if (
      setting.collectionStatus(payment.payment_note).payment_code === '04' ||
      setting.collectionStatus(payment.payment_note).payment_code === '05' ||
      setting.collectionStatus(payment.payment_note).payment_code === '06' ||
      (setting.collectionStatus(payment.payment_note).payment_code === '01' && payment.is_ptp === false) ||
      (setting.collectionStatus(payment.payment_note).payment_code === '01' &&
        payment.is_ptp === true &&
        payment.ptp_location === 'Field' &&
        new Date(payment.ptp_date).getMonth() === getCurrentDate().getMonth()) ||
      (payment.is_ptp === true && new Date(payment.ptp_date).getMonth() === getCurrentDate().getMonth())
    )
      return true;

    // if (setting.collectionStatus(payment.payment_note).payment_code === '01' && payment.is_ptp === false) return true;

    // if (
    //   setting.collectionStatus(payment.payment_note).payment_code === '01' &&
    //   payment.is_ptp === true &&
    //   payment.ptp_location === 'Field' &&
    //   new Date(payment.ptp_date).getMonth() === getCurrentDate().getMonth()
    // )
    //   return true;

    // if (payment.is_ptp === true && new Date(payment.ptp_date).getMonth() === getCurrentDate().getMonth()) return true;

    return false;
  };

  static isIncreasedPTP = (payment) => {
    if (
      (setting.collectionStatus(payment.payment_note).payment_code === '01' &&
        payment.is_ptp === true &&
        payment.ptp_location === 'Field' &&
        new Date(payment.ptp_date).getMonth() === getCurrentDate().getMonth()) ||
      (payment.is_ptp === true && new Date(payment.ptp_date).getMonth() === getCurrentDate().getMonth())
    )
      return true;
    return false;
  };
}

module.exports = ItineraryController;
Editor is loading...