Skip to content

Instantly share code, notes, and snippets.

@hrdtbs
Last active June 4, 2022 10:57
Show Gist options
  • Save hrdtbs/533980dfd21414e7dd6682ff609133e5 to your computer and use it in GitHub Desktop.
Save hrdtbs/533980dfd21414e7dd6682ff609133e5 to your computer and use it in GitHub Desktop.
jobcanの交通費明細で有効なCSV形式
// source: https://ssl.wf.jobcan.jp/static/wf/scripts/controllers/create_request/expense_specifics_transport_controllers.js?ci-build-64159.444028469786320462
(function() {
'use strict';
angular
.module('WfApp.create_request_controllers')
.controller('ExpenseSpecificsTransportController', ExpenseSpecificsTransportController);
ExpenseSpecificsTransportController.$inject = [
'$http',
'$scope',
'$rootScope',
'$uibModal',
'gettextCatalog',
'notifyService',
'NorikaeService',
'Const',
'CreateRequestExpenseSpecificsService',
];
/**
* 経費精算 交通費明細
* @param {Object} $http
* @param {Object} $scope
* @param {Object} $rootScope
* @param {Object} $uibModal
* @param {Object} gettextCatalog
* @param {Object} notifyService
* @param {Object} NorikaeService
* @param {Object} Const
* @param {Object} CreateRequestExpenseSpecificsService
*/
function ExpenseSpecificsTransportController(
$http, $scope, $rootScope, $uibModal, gettextCatalog,
notifyService, NorikaeService, Const, CreateRequestExpenseSpecificsService) {
$scope.selectICcardHistoryCSV = selectICcardHistoryCSV;
$scope.selectICcardHistoryFelica = selectICcardHistoryFelica;
$scope.showNorikaeDialog = showNorikaeDialog;
$scope.showSelectStationDialog = showSelectStationDialog;
$scope.changeStation = changeStation;
$scope.changeRoundTrip = changeRoundTrip;
$scope.changeTransportRow = changeTransportRow;
$scope.isValidStationName = isValidStationName;
$scope.existInRequest = existInRequest;
$scope.isDraftRequest = isDraftRequest;
$scope.getProgressRequestUrl = getProgressRequestUrl;
$scope.isTrafficWayRequired = isTrafficWayRequired;
$scope._ = _;
// definitions //
/**
* ICカード利用履歴csvアップロード
* @param {Object} file
* @param {Object} sheet
*/
function selectICcardHistoryCSV(file, sheet) {
var error_message = gettextCatalog.getString(
'エラーが発生しました。<br>ICカードリーダーから読み込んだCSVファイルを選択してください。');
if (!file) {
return;
}
// 拡張子がcsv以外の場合
var reg=/(.*)(?:\.([^.]+$))/;
var extension = file.name.match(reg)[file.name.match(reg).length-1];
if (extension !== 'csv') {
notifyService.showNotify(error_message, 'alert-danger');
return;
}
var params = {};
var list = [];
// ここだけPapaをESLint許容する。
// eslint-disable-next-line no-undef
Papa.parse(file, {
header: true,
encoding: 'Shift-JIS',
complete: function(results) {
// 正しいCSVファイルかを確認する
if (!results.data[0].hasOwnProperty('__parsed_extra') ||
results.data[0]['__parsed_extra']['0'] !== '定期') {
notifyService.showNotify(error_message, 'alert-danger');
return;
}
// 最初のヘッダーは削除する
results.data.splice(0, 1); // ヘッダー行削除
angular.forEach(results.data, function(record) {
if (record['__parsed_extra']) {
// バス(メモにバス/路面等が入ってる場合)
if (record['__parsed_extra'][8].match(/バス\/路面等/)) {
list.push({
start_place: record['__parsed_extra'][8],
goal_place: record['__parsed_extra'][8],
amount: record['__parsed_extra'][6],
allocation_date: record[Object.keys(record)[0]],
check_flg: false,
});
}
// 電車(出発駅、到着駅が入ってる場合)
if (record['__parsed_extra'][2] !== '' && record['__parsed_extra'][5] !== '') {
list.push({
start_place: record['__parsed_extra'][2],
goal_place: record['__parsed_extra'][5],
amount: record['__parsed_extra'][6],
allocation_date: record[Object.keys(record)[0]],
check_flg: false,
});
}
}
});
// 古い順に並び替え
params.iccard_history = list.reverse();
// 基本的には↑の reverse にて古い順になっているものの、
// CSVをユーザーが勝手に編集した場合などがあるため、allocation_date で再度並び替えておく。
// (注:allocation_date の並び替えだけでは、同一日内での順序は古い順にはならない。
// 同一日内での順序は、生CSVの順序に頼るしかない。)
params.iccard_history.sort(function(a, b) {
return Date.parse(a['allocation_date']) - Date.parse(b['allocation_date']);
});
params.sheet = sheet;
params.default_group = $scope.form_data.group;
params.default_project = _getProjectForSpecificsRow();
$uibModal.open({
templateUrl: '../static/wf/views/create_request/dialogs/iccard_history_templ.html',
controller: 'ICcardHistoryDialogCtrl',
resolve: {
params: function() {
return params;
},
},
});
}, error: function() {},
});
}
/**
* 勤怠打刻時の交通費履歴選択
* @param {Object} sheet
*/
function selectICcardHistoryFelica(sheet) {
var params = {};
params.sheet = sheet;
params.all_sheets = $scope.form_data.expense.request_expense_specifics;
params.default_group = $scope.form_data.group;
params.default_project = _getProjectForSpecificsRow();
params.is_felica = true;
$http.get('/api/v1/felica/').then(function(res) {
params.iccard_history = res.data;
$uibModal.open({
templateUrl: '../static/wf/views/create_request/dialogs/iccard_history_templ.html',
controller: 'ICcardHistoryDialogCtrl',
resolve: {
params: function() {
return params;
},
},
});
});
}
/**
* 経費・支払明細のプロジェクト用オブジェクトを返却する
* @return {null|{id: *, name: *, project_code: *}}
* @private
*/
function _getProjectForSpecificsRow() {
var project_obj;
if (angular.isUndefinedOrNull($scope.form_data.project)) {
project_obj = null;
} else {
project_obj = {id: $scope.form_data.project,
name: $scope.form_data.project_name,
project_code: $scope.form_data.project_code};
}
return project_obj;
}
/**
* 交通費明細 乗換案内ダイアログ
* @param {Object} row
* @param {Object} station_kind
* @param {Object} sheet 明細
* @param {Number} index 明細行のインデックス
*/
function showNorikaeDialog(row, station_kind, sheet, index) {
if (angular.isUndefinedOrNull(station_kind)) {
station_kind = 'start';
}
// ICリーダーの明細では、基本的に当関数は呼ばれることはなく、
// 駅名不明の場合のみ当関数を呼び出すことができる。
// 駅名不明の場合は、経路検索ではなく、直接、駅名検索を行う。
if (row.is_icreader) {
$scope.showSelectStationDialog(row, station_kind);
return;
}
// 乗換案内は無料制限ではないこと
$http.get('/api/v1/check_current_subscription/').then(function(res) {
if (res.data.subscription_flg) {
var params = {
allocation_date: row.allocation_date,
start_station_detail: row.start_station_detail,
goal_station_detail: row.goal_station_detail,
start_station_name: row.start_place,
goal_station_name: row.goal_place,
via_station_name: row.via_place,
station_kind: station_kind,
traffic_way: row.traffic_way || '',
pay_content: row.pay_content || '',
group: row.group || '',
project: row.project || '',
specifics_group_setting: sheet.specifics_group_setting,
specifics_project_setting: sheet.specifics_project_setting,
};
if (!angular.isUndefinedOrNull($scope.requester_id)) {
// 代理申請の場合
params.user_id = $scope.requester_id;
} else if (!angular.isUndefinedOrNull($scope.form_data.request_user)) {
// 修正の場合
params.user_id = $scope.form_data.request_user.id;
}
var size = '';
if ($rootScope.is_mobile) {
size = 'mobile';
}
var modalInstance = $uibModal.open({
templateUrl: '../static/wf/views/create_request/dialogs/norikae_templ.html',
controller: 'norikaeDialogCtrl',
size: size,
resolve: {
params: function() {
return params;
},
}});
modalInstance.result.then(function(norikae_datas) {
var base_row = sheet.request_expense_specifics_rows[index];
norikae_datas.forEach(function(norikae_data, data_index) {
if (data_index) {
$scope.addSpecificsDetail(sheet, index + data_index - 1);
}
row = sheet.request_expense_specifics_rows[index + data_index];
row.allocation_date = norikae_data.allocation_date;
row.start_place = norikae_data.start_place;
row.goal_place = norikae_data.goal_place;
row.via_place = norikae_data.via_place;
row.round_trip_flg = base_row.round_trip_flg;
row.traffic_way = norikae_data.traffic_way;
row.pay_content = norikae_data.pay_content || '';
if (norikae_data.group || row.group) {
row.group = norikae_data.group;
}
if (norikae_data.project || row.project) {
row.project = norikae_data.project;
}
if (norikae_data.is_manual_input ===
Const.miscellaneous.NORIKAE_ICON_EXCLAMATION.MANUAL_INPUT
) {
$scope.changeTransportRow(row);
row.amount = null;
} else {
row.start_station_detail = norikae_data.start_station_detail;
row.goal_station_detail = norikae_data.goal_station_detail;
// 往復が指定されている場合は倍にして返す
row.amount = (row.round_trip_flg === 1)? norikae_data.amount * 2: norikae_data.amount;
row.is_lowprice = Number(norikae_data.is_lowprice);
row.is_early = Number(norikae_data.is_early);
row.is_iccard = Number(norikae_data.is_iccard);
row.is_teiki = Number(norikae_data.is_teiki);
row.is_norikae_input = 1;
row.is_manual_input = Const.miscellaneous.NORIKAE_ICON_EXCLAMATION.AUTO_INPUT;
row.is_icreader = 0;
}
});
$scope.checkExtraFlow('amount', null);
}).catch(function() {
});
} else {
notifyService.showNotify(gettextCatalog.getString('無料プランのため、乗換案内機能はご利用できません。'), 'alert-danger');
}
});
}
/**
* 駅名検索ダイアログ
* @param {Object} row
* @param {Object} kind
*/
function showSelectStationDialog(row, kind) {
var modalInstance = NorikaeService.openStationSearchDialog('', $rootScope.is_mobile);
modalInstance.result.then(function(station_detail) {
if (kind === 'start') {
row.start_station_detail = station_detail;
row.start_place = station_detail.name;
} else if (kind === 'goal') {
row.goal_station_detail = station_detail;
row.goal_place = station_detail.name;
}
}, function() {
});
}
/**
* 駅名の手動変更の際の、乗換案内データのクリア
* @param {Object} row
* @param {Object} station_type
*/
function changeStation(row, station_type) {
if (station_type === 'start') row.start_station_detail = null;
if (station_type === 'goal') row.goal_station_detail = null;
$scope.changeTransportRow(row);
}
/**
* 交通手段の必須確認
* @param {Object} requestForm
* @return {boolean}
* @param {Object} sheet
* @param {Object} sheet_idx
* @param {Object} index
*/
function isTrafficWayRequired(requestForm, sheet, sheet_idx, index) {
var formSheetKey = 'expenseSpecificsForm_' + sheet_idx;
var trafficWayKey = 'traffic_way_' + sheet_idx + '_' + index;
return formSheetKey in requestForm
&& trafficWayKey in requestForm[formSheetKey]
&& $scope.clicked_confirm
&& sheet.validate_error
&& requestForm[formSheetKey][trafficWayKey].$invalid === true;
}
/**
* 交通費明細 片道 <-> 往復 の変更時の金額自動変更
* @param {Object} row
*/
function changeRoundTrip(row) {
if (row.is_credit_card) {
return;
}
calcAmountWithRoundTrip(row);
$scope.checkExtraFlow('amount', null);
}
/**
* 交通費 片道 <->往復変更時の金額計算処理
* @param {Object} row
*/
function calcAmountWithRoundTrip(row) {
// 片道 -> 往復
if (row.round_trip_flg) {
row.amount *= 2;
}
// 往復 -> 片道
else {
row.amount /= 2;
}
}
/**
* 交通費明細を手動入力した場合の各種マーク変更
* @param {Object} row
*/
function changeTransportRow(row) {
row.is_lowprice = 0;
row.is_early = 0;
row.is_iccard = 0;
row.is_teiki = 0;
row.is_icreader = 0;
row.is_norikae_input = 0;
row.is_manual_input = Const.miscellaneous.NORIKAE_ICON_EXCLAMATION.MANUAL_INPUT;
row.progress_request = null; // コピー申請で進行中交通費申請が紐付いている場合も、行コピー時には無効化する
}
/**
* 正しい駅名ならtrue、駅名不明ならfalseを返す
* @param {String} station_name
* @return {boolean}
*/
function isValidStationName(station_name) {
var is_not_valid =
station_name === Const.miscellaneous.UNKNOWN_STATION_BUS || // バス停の場合
station_name === Const.miscellaneous.UNKNOWN_STATION_TRAIN || // 勤怠からの交通費連携で、駅名がDBに無い場合
station_name === '';
return !is_not_valid;
}
/**
* 勤怠からの交通費連携で、申請済の交通費での明細行あれば true を返す
* 交通費選択ダイアログではこのような交通費は選択できないが、コピー申請にてこのような明細行が発生しうる
* @param {Object} row 行オブジェクト
* @return {boolean}
*/
function existInRequest(row) {
return CreateRequestExpenseSpecificsService.existInRequest(row);
}
/**
* 行に書かれた交通費の情報がまだ下書きの申請のものであればtrueを返す
* @param {Object} row 行オブジェクト
* @return {boolean}
*/
function isDraftRequest(row) {
return CreateRequestExpenseSpecificsService.isDraftRequest(row);
}
/**
* その行の交通費データを使っている申請へのURLを取得する
* @param {Object} row 行オブジェクト
* @return {string}
*/
function getProgressRequestUrl(row) {
return CreateRequestExpenseSpecificsService.getProgressRequestUrl(row);
}
}
})();
@hrdtbs
Copy link
Author

hrdtbs commented Jun 4, 2022

selectICcardHistoryCSVのCSVパース部分

Papa.parse(file, {
    header: true,
    encoding: 'Shift-JIS',
    complete: function(results) {
        // 正しいCSVファイルかを確認する
        if (!results.data[0].hasOwnProperty('__parsed_extra') ||
            results.data[0]['__parsed_extra']['0'] !== '定期') {
            notifyService.showNotify(error_message, 'alert-danger');
            return;
        }
        // 最初のヘッダーは削除する
        results.data.splice(0, 1); // ヘッダー行削除
        angular.forEach(results.data, function(record) {
            if (record['__parsed_extra']) {
                // バス(メモにバス/路面等が入ってる場合)
                if (record['__parsed_extra'][8].match(/バス\/路面等/)) {
                    list.push({
                        start_place: record['__parsed_extra'][8],
                        goal_place: record['__parsed_extra'][8],
                        amount: record['__parsed_extra'][6],
                        allocation_date: record[Object.keys(record)[0]],
                        check_flg: false,
                    });
                }

                // 電車(出発駅、到着駅が入ってる場合)
                if (record['__parsed_extra'][2] !== '' && record['__parsed_extra'][5] !== '') {
                    list.push({
                        start_place: record['__parsed_extra'][2],
                        goal_place: record['__parsed_extra'][5],
                        amount: record['__parsed_extra'][6],
                        allocation_date: record[Object.keys(record)[0]],
                        check_flg: false,
                    });
                }
            }
        });
        // 古い順に並び替え
        params.iccard_history = list.reverse();
        // 基本的には↑の reverse にて古い順になっているものの、
        // CSVをユーザーが勝手に編集した場合などがあるため、allocation_date で再度並び替えておく。
        // (注:allocation_date の並び替えだけでは、同一日内での順序は古い順にはならない。
        // 同一日内での順序は、生CSVの順序に頼るしかない。)
        params.iccard_history.sort(function(a, b) {
            return Date.parse(a['allocation_date']) - Date.parse(b['allocation_date']);
        });

        params.sheet = sheet;
        params.default_group = $scope.form_data.group;
        params.default_project = _getProjectForSpecificsRow();
        $uibModal.open({
            templateUrl: '../static/wf/views/create_request/dialogs/iccard_history_templ.html',
            controller: 'ICcardHistoryDialogCtrl',
            resolve: {
                params: function() {
                  return params;
                },
            },
        });
    }, error: function() {},
});

@hrdtbs
Copy link
Author

hrdtbs commented Jun 4, 2022

以下の形式はjobcanの交通費明細で有効
encoding: Shift-JIS

allocation_date
9999/12/31,定期,この行は読み込まれません
2022/6/2,dummy,dummy,飯田橋,dummy,dummy,池袋,2000,dummy,dummy,dummy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment