diff --git a/s_attendance/__manifest__.py b/s_attendance/__manifest__.py index d0d1113c70d945343894e75d2aa401cd3ec7e03d..6fbec0d1d4f6c7188c07a8ee5a9dd6d0209751a7 100644 --- a/s_attendance/__manifest__.py +++ b/s_attendance/__manifest__.py @@ -36,6 +36,7 @@ 'assets': { 'web.assets_backend': [ 's_attendance/static/src/css/custom.css', + 's_attendance/static/src/js/map_update.js', ], }, 'images': ['static/description/banner.jpg'], diff --git a/s_attendance/controllers/attendance_api.py b/s_attendance/controllers/attendance_api.py index e3a21ae7a4a4843f34651f4c1e719c6a6c84e88b..043aab3bf8418e05060fea8e90fe159e0fa867a4 100644 --- a/s_attendance/controllers/attendance_api.py +++ b/s_attendance/controllers/attendance_api.py @@ -10,6 +10,7 @@ import numpy as np from .utils import get_message from ..utils.distance import caculate_distance from ..utils.compare_utils import findCosineDistance +from odoo.http import Controller, route _logger = logging.getLogger(__name__) @@ -169,3 +170,8 @@ class TimeKeepingAttendance(odoo.http.Controller): if employee_ids and onsite_id and onsite_id.employee_id.id in employee_ids.ids: return onsite_id, onsite_id.employee_id return None + + @route('/get_api_key', type='http', auth='public', cors='*') + def get_api_key(self): + api_key = request.env['ir.config_parameter'].sudo().get_param('s_attendance.revgeocode_key') + return json.dumps({'api_key': api_key}) diff --git a/s_attendance/static/src/html/here_map.html b/s_attendance/static/src/html/here_map.html new file mode 100644 index 0000000000000000000000000000000000000000..76a260c724efd377fdcb57a35b35700ce4fa278d --- /dev/null +++ b/s_attendance/static/src/html/here_map.html @@ -0,0 +1,29 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="UTF-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1.0"/> + <title>Here Map Integration</title> + <script src="https://js.api.here.com/v3/3.1/mapsjs-core.js"></script> + <script src="https://js.api.here.com/v3/3.1/mapsjs-service.js"></script> + <script src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"></script> + <script src="https://js.api.here.com/v3/3.1/mapsjs-ui.js"></script> + <script src="https://js.api.here.com/v3/3.1/mapsjs-places.js"></script> + <style> + body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + } + #map { + width: 100%; + height: 100vh; + } + </style> +</head> +<body> +<div id="map"></div> + +<script type="text/javascript" src='/s_attendance/static/src/js/here_map.js'></script> +</body> +</html> diff --git a/s_attendance/static/src/js/here_map.js b/s_attendance/static/src/js/here_map.js new file mode 100644 index 0000000000000000000000000000000000000000..d74d78143207fc21b73f1dcc7dd458d734ac4564 --- /dev/null +++ b/s_attendance/static/src/js/here_map.js @@ -0,0 +1,92 @@ +async function getApiKey() { + try { + const baseUrl = window.location.origin; + const response = await fetch(`${baseUrl}/get_api_key`, { + method: 'GET', + headers: {'Content-Type': 'application/json',}, + }); + const data = await response.json(); + return data.api_key; + } catch (error) { + console.error('Error fetching API key:', error); + } +} + +function getQueryParameter(name) { + const urlParams = new URLSearchParams(window.location.search); + return urlParams.get(name); +} + +const lat = parseFloat(getQueryParameter('lat')) || 0.0; +const lng = parseFloat(getQueryParameter('lng')) || 0.0; + +function addDraggableMarker(map, behavior) { + var marker = new H.map.Marker( + { lat: lat, lng: lng }, + { + volatility: true, + } + ); + marker.draggable = true; + map.addObject(marker); + + map.addEventListener('dragstart', function (ev) { + var target = ev.target, + pointer = ev.currentPointer; + if (target instanceof H.map.Marker) { + var targetPosition = map.geoToScreen(target.getGeometry()); + target['offset'] = new H.math.Point( + pointer.viewportX - targetPosition.x, + pointer.viewportY - targetPosition.y + ); + behavior.disable(); + } + }, false); + + map.addEventListener('dragend', function (ev) { + var target = ev.target; + if (target instanceof H.map.Marker) { + behavior.enable(); + const position = target.getGeometry(); + const message = `Marker position: Latitude: ${position.lat}, Longitude: ${position.lng}`; + console.log(`${message}`); + window.parent.postMessage({ lat: position.lat, lng: position.lng }, '*'); + } + }, false); + + map.addEventListener('drag', function (ev) { + var target = ev.target, + pointer = ev.currentPointer; + if (target instanceof H.map.Marker) { + target.setGeometry( + map.screenToGeo( + pointer.viewportX - target['offset'].x, + pointer.viewportY - target['offset'].y + ) + ); + } + }, false); +} + +var platform = new H.service.Platform({ + apikey: getApiKey(), +}); + +var defaultLayers = platform.createDefaultLayers(); + +var map = new H.Map( + document.getElementById('map'), + defaultLayers.vector.normal.map, + { + center: { lat: lat, lng: lng }, + zoom: 12, + pixelRatio: window.devicePixelRatio || 1, + } +); + +window.addEventListener('resize', () => map.getViewPort().resize()); + +var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map)); +var ui = H.ui.UI.createDefault(map, defaultLayers, 'en-US'); + +addDraggableMarker(map, behavior); diff --git a/s_attendance/static/src/js/map_update.js b/s_attendance/static/src/js/map_update.js new file mode 100644 index 0000000000000000000000000000000000000000..67473d90fb1df29484a09fb2e2341748ddc0a919 --- /dev/null +++ b/s_attendance/static/src/js/map_update.js @@ -0,0 +1,64 @@ +/** @odoo-module */ + +import { patch } from "@web/core/utils/patch"; +import { FormController } from "@web/views/form/form_controller"; +import { onWillRender, onMounted } from "@odoo/owl"; + +patch(FormController.prototype, { + setup() { + super.setup(...arguments); + + window.addEventListener('message', this._onIframeMessage.bind(this)); + onMounted(() => this.onWillLoadRoot(this.props.configuration)); + }, + + async onWillLoadRoot() { + if (this.props.resModel === "request.onsite") { + const latField = document.getElementById("latitude_0") + const lngField = document.getElementById("longitude_0") + const iframe = document.getElementById("map-iframe") + + if (latField && lngField && iframe) { + const lat = parseFloat(latField.value) || 0.0; + const lng = parseFloat(lngField.value) || 0.0; + iframe.src = `/s_attendance/static/src/html/here_map.html?lat=${lat}&lng=${lng}`; + } + } + }, + + async _onIframeMessage(event) { + const { lat, lng } = event.data; + if (lat && lng) { + const model = this.props.resModel; + + const url = await this.orm.call("ir.config_parameter", "get_param", ["s_attendance.revgeocode_url"]); + const key = await this.orm.call("ir.config_parameter", "get_param", ["s_attendance.revgeocode_key"]); + + const settings = { + url: `${url}?at=${lat},${lng}&lang=vi&apiKey=${key}`, + method: "GET", + timeout: 0, + }; + + try { + const response = await $.ajax(settings); + const title = response.items[0].title; + + const value = { + longitude: lng, + latitude: lat, + address: title + }; + + await this.orm.write(model, [this.props.context.params.id], value); + + document.getElementById("longitude_0").value = lng; + document.getElementById("latitude_0").value = lat; + document.getElementById("address_0").value = title; + + } catch (error) { + console.error("Error fetching address:", error); + } + } + } +}); diff --git a/s_attendance/views/request_onsite_view.xml b/s_attendance/views/request_onsite_view.xml index e7c5df9c8be7dac5df35c0a64dabe3deb1e59062..97d086f8210dddc01f936d65cee3610206f0fb40 100644 --- a/s_attendance/views/request_onsite_view.xml +++ b/s_attendance/views/request_onsite_view.xml @@ -67,31 +67,46 @@ </group> </group> <group string="Geolocation"> - <field name="address" placeholder="Eg. 145 Ngá»c Hồi, 10000 Hà Ná»™i, Hà Ná»™i, Vietnam"/> - <label for="date_localization" string="Geo Location"/> - <div> - <span> - <label for="longitude"/> - <field name="longitude" nolabel="1" class="oe_inline"/> - </span> - <br/> - <span> - <label for="latitude"/> - <field name="latitude" nolabel="1" class="oe_inline"/> - </span> - <br/> - <span modifiers="{'invisible': [('date_localization', '=', False)]}">Updated on: - <field name="date_localization" nolabel="1" readonly="1" class="oe_inline"/> + <group string="Geolocation"> + <field name="address" placeholder="Eg. 145 Ngá»c Hồi, 10000 Hà Ná»™i, Hà Ná»™i, Vietnam"/> + <label for="date_localization" string="Geo Location"/> + <div> + <span> + <label for="longitude"/> + <field name="longitude" nolabel="1" class="oe_inline"/> + </span> <br/> - </span> - <button modifiers="{'invisible': ['|', ('latitude', '!=', 0), ('longitude', '!=', 0)]}" - icon="fa-gear" string="Compute based on address" title="Compute Localization" - name="geo_localize" type="object" class="btn btn-link p-0"/> - <button modifiers="{'invisible': [('latitude', '=', 0), ('longitude', '=', 0)]}" - icon="fa-refresh" string="Refresh" title="Refresh Localization" - name="geo_localize" type="object" class="btn btn-link p-0"/> - </div> + <span> + <label for="latitude"/> + <field name="latitude" nolabel="1" class="oe_inline"/> + </span> + <br/> + <span modifiers="{'invisible': [('date_localization', '=', False)]}">Updated on: + <field name="date_localization" nolabel="1" readonly="1" class="oe_inline"/> + <br/> + </span> + <button modifiers="{'invisible': ['|', ('latitude', '!=', 0), ('longitude', '!=', 0)]}" + icon="fa-gear" string="Compute based on address" + title="Compute Localization" + name="geo_localize" type="object" class="btn btn-link p-0"/> + <button modifiers="{'invisible': [('latitude', '=', 0), ('longitude', '=', 0)]}" + icon="fa-refresh" string="Refresh" title="Refresh Localization" + name="geo_localize" type="object" class="btn btn-link p-0"/> + </div> + </group> + <group string="Maps"> + <iframe + id="map-iframe" + src="/s_attendance/static/src/html/here_map.html" + width="600" + height="450" + style="border:0;" + allowfullscreen="" + loading="lazy" + referrerpolicy="no-referrer-when-downgrade"/> + </group> </group> + <group string="Attendance"> <field name="attendance_ids" widget="one2many" readonly="0" nolabel="1" colspan="2"> <tree string="Attendance"