From 595dccc17d667b3dd41c633933f95f0e8bcb9f4f Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Tue, 17 Mar 2026 13:32:08 -0400 Subject: [PATCH] changes --- .../controllers/portal_main.py | 2 +- .../__pycache__/portal_clock.cpython-312.pyc | Bin 13989 -> 15266 bytes fusion_clock/controllers/clock_api.py | 3 + fusion_clock/controllers/portal_clock.py | 27 ++++- .../static/src/js/fusion_clock_portal.js | 95 +++++++++++++++++- fusion_clock/views/portal_clock_templates.xml | 16 +-- .../views/portal_timesheet_templates.xml | 26 ++--- .../controllers/portal_schedule.py | 2 +- .../static/src/css/fusion_task_map_view.scss | 8 ++ .../static/src/js/fusion_task_map_view.js | 3 + .../static/src/xml/fusion_task_map_view.xml | 5 + 11 files changed, 159 insertions(+), 28 deletions(-) diff --git a/fusion_authorizer_portal/controllers/portal_main.py b/fusion_authorizer_portal/controllers/portal_main.py index f10dbb2b..1b7214d3 100644 --- a/fusion_authorizer_portal/controllers/portal_main.py +++ b/fusion_authorizer_portal/controllers/portal_main.py @@ -1148,7 +1148,7 @@ class AuthorizerPortal(CustomerPortal): ('check_out', '=', False), ], limit=1) if att: - check_in_time = (att.check_in.isoformat() + 'Z') if att.check_in else '' + check_in_time = att.check_in.isoformat() if att.check_in else '' location_name = att.x_fclk_location_id.name if att.x_fclk_location_id else '' from datetime import datetime diff --git a/fusion_clock/controllers/__pycache__/portal_clock.cpython-312.pyc b/fusion_clock/controllers/__pycache__/portal_clock.cpython-312.pyc index e2816935a904c62ef1ec67b7271fcdbe1535ca69..9d5f15ad726af34ddb8243c37ec6a6a90cfad9a7 100644 GIT binary patch delta 3484 zcmai1ZERE58NTQGBaRb0j=$}E*-322Byso(tP738AV44#8W^;c;2GabNbncu-ZUh; z9w1c(x@v=+sv29?cHPQ|&_<_7El8;JUm4q^Fqn#NQOkZ!(|#<#l>zRn5m)P>+ihygs{d!LV6X(RhXW%y_arBw(Pgcbyhp0)z0 z)smQ^>4V91emp1BD2U<~+JPF2CK9PcJJC9UvMM<*q$VMr6qRWON=&-r3g}BXe@Jh~ z=t>yS=QxgKtanKF>_Al)*`@{PLxmq8ZZ1cp8pkD(Y9Y}G8fcLiv`A_0Ht6Dq-{&B*6MpuNEo|_g^0VH?EbFrW z#^4{bvt=F^t6S<~=9-Ahe}D*8uUSgRR5YV)O_BK7Y1sL>8nMcMfrt5^fu_$fx}f>Q zAwsXUHV^n#ZCP5u0?r>S*j#aXmyd8om*QN{4S&pWMYrM_3+t0DM#U}T329Anv%Af1 z=5;jr{6hT&qb_}lzTsJVn_p-w>0#ETF1MXVADIY5xKQUJ!yFNsgwV99v>$|f^RkhX z;|tMoV@ueVPG5~D&5v+tes~Lflt=O4UIi7;7~Y}B8b}kY;0uUG=q-Ir2}6)Bf1(uc z(ql~sPn*y1Z*Dch(nU`x#G?0lW_|sjR40Xua{QS|N{S#pGyCwP>CExD4rvNlBNme*cPEjSec2p5)Pd zPNH4xl>fc{1{y*WBm&h!MPZbRX<4TnjcqDNL^)YV(NtE?@&UBQSyjt{(TpsD3vFh( zK=?Uxs!$Mf0?Ko+jw{TAPN%I%3hLh`VS37^W_0-nezTlnuj`KS%?w#3tW%bpb z%RSdDb3?ms*6+Sk*M5H8#le}uE3vEXm)k!*GWX)QZ`SR4g7}r2b$f5`7eC*h|I_}C z{4H&|;hEtp8?SD;yycpIZpZMg`h9cbllQ93b@qRAW?Rj?fpa(h%gs6)J~IEYW36Lb z5Bpm~tL-7T3TEK=cWfb6uo!218^2}LX|84w%{?SE*rSC8G>0^=HJy-{H{9ei2%!>9 zJw+dWmcGXzG?(;>d#tUrLirb1Z_&DoEtowVJG#gp*v|~~@EH36ak!L9#i|g+B)6C5 zRUCq&L=lUmwNI2f1$)U>XcZ#U)}p6GK%0Ppp1_+zRPmHtc5w07RA5L<37z>|W-^4? zDIF9=IrQqNIFX?J5EI?NqAgp1nP^J~d6~W2(nfsjT1!)FnQF9`SW7D;7?rG{d*OG6 z(0!~iDt92o#CbjCsweXFkSHV%=EtEH_&VjV3#~omCi`paOv@rSx&s4wiuEn_k4S=? z#icAIANG@I(D@YmKQlg-=s$}yCIO~Qk^P~FK)(b11q!1n=<7Ttq<}}#Fr)&?tPgic zf|4k~*2fMK%6=VdtM`@3ib`mY_f#)IWOgsMHgy{PE2vn=q|zc~sKs)kT%gcbad-sb zQJ$jTMVSH+_v$sw`zRJhkzj3bd8}%d^diIQuH-pM5L- z7jpD&pzfso3s)mjOYrmR@Qv#5GsxxZob&R{`aS=LT%OJ?6ZgDT=9V(KG|juYK=^iD zoE6&#pi=y-Jx3hcHGTs>)`j3^{heKrx1p=rv@$OVrvf`A!PBU{M0iR#rj(uSjJ*&- zCwzq-KUhqD{Tb5^$D|)l`CL9my|#-c=xMbqY@|KWP42VxiE|L%-xI&$tIFYh$bPo$ za90T3p0J*3-cR!rlBk-+Bcl?8r+rP7J->Xwi;1pQ>TzSipa;RGmv_KB|GVY=yqU~8 zRuDqEnR8`tvI&=fdYYpl1Po6#QFxa{dIAL(5iTKILzOL=OlMM(lom6YWHL_Z3XnP;=i9N^n%sHaxcU_8JYrSRK z_D_BTF%4?A+rd^F%)i7Mx`&)vX@j%JJ@ivtWRe~4{^@!xLsTpP2J<5$Q0rd@omv6^ zaI_IR%y3k5#`sjrnrvPe&xpPBHViRvsh_>Fs?}Jy!4=rCRqonBBeDJ%?h*cKj{gJP C6(KwT delta 2656 zcmZ`*TWlN072R2qONyc-zC=ApOQI}^T8Vm6jT}p|lg3W$+D=^AaRaB;mgcS{N~B27 zF0IH|RSsaliJLYS28o?U1q!2Sqq;>u)EC+m$!D4uEVzY>1uCdtjsMg-1!{lw-dR1| z#Ga4CnKO6p+`Drhygu_zzVhRW3aiU2g=YlSeNj+7n(u~VgAFZwHcCVmC!NbXsK>-yo!6`Jd97I3EC6 z8bk;ov;o`~_l(G1x&x+GR4G!en5LRCQOIjbu|SPE5a7l4kR2GrZC8`o}oiFRj z&-P?;h0GM?ChcW&746kN*h2sAUX%pkQsk;&3X_Db1?rubBdyUa--b-Zd0BtG!huscJGJGv3#!D*a%!fU~A!Uz1nD2Z)!LO_Yr>Qj376$Uss86T*l% zZh13>d@(&et7KIgV!6nhNqm0Gr{#;e!mOqNuUifPS+pGR%62f|OEf{4vF@6)eHaF` z3gBtFofBasx8jXw2OR`XcF<~ev*vpw#lBs;5|1ZqmDK*5)Jk6%;HH^Hm(^8 zt1=YATJhr=szB=cHbkwp?l{U(~6LVq*vj!gmnT0G5kFcB+;{ zwP{tOa*ds!;?#`gIQZ02I)Q>&6!aY4u#IMr>qbx!h7jBUme(k#=~*Rh7&_J1{b*nF z8KgmXMuQ4)1O+D%Fs|$us9|HVI>!_61ZM0pR|4Muzie+RvAh~96YGO@SECEjYsyNX z>vo`jCD4B>FmO9Cv=SI%q%mAG;$4@7VEkQi-7N%%?+T7!@)hX|ANynDZ>5jApY$Id zVKYrFVQvdTnE+6On&{j4`}2m;znB*_bGDbc!6$z2n3a+abzJB9?6)m=+nPFT!Gp&{M3v!$@Li$|9|XAImpWpi`O(rMv{S z_p71GZgvcjkJ!K3mv%mIjE{if7n|>#kfvbcd^20<4|i_uLJo{;a>#B$*feMk zVJlH+o+Xn{9=U`%-O#~u>5N7%qX^dX?G8Z;xYQ9006A^XURyz zEKDum(biB|H826gbe$o>T3mk58ttqMiS z3{XKU5@!mbp64wE8*C9yz$-2lF>l30N`7a#5 zbn@ksH~nqzp7?#+hb@0`J@;>M*manT9tBaHukT~Q-J`*QO|=kSzrp!gc5e6Edo3*H zhYRE5@W17Kpe?tOvsY-xl0f-6G|ci0=L>2nr|qGi!V)dkzs{Z?YH1r=^G07u-1bIS bywPh1Zh2cTxWABQ9mIRB@;?IpawY!*(xFw` diff --git a/fusion_clock/controllers/clock_api.py b/fusion_clock/controllers/clock_api.py index 38aeba1c..3c1095c1 100644 --- a/fusion_clock/controllers/clock_api.py +++ b/fusion_clock/controllers/clock_api.py @@ -343,6 +343,7 @@ class FusionClockAPI(http.Controller): 'attendance_id': attendance.id, 'check_in': fields.Datetime.to_string(attendance.check_in), 'location_name': location.name, + 'location_address': location.address or '', 'message': f'Clocked in at {location.name}', 'streak': employee.x_fclk_ontime_streak, } @@ -389,6 +390,7 @@ class FusionClockAPI(http.Controller): 'break_minutes': attendance.x_fclk_break_minutes, 'overtime_hours': round(attendance.x_fclk_overtime_hours or 0, 2), 'location_name': location.name, + 'location_address': location.address or '', 'message': f'Clocked out from {location.name}', } @@ -527,6 +529,7 @@ class FusionClockAPI(http.Controller): 'attendance_id': att.id, 'check_in': fields.Datetime.to_string(att.check_in), 'location_name': att.x_fclk_location_id.name or '', + 'location_address': att.x_fclk_location_id.address or '', 'location_id': att.x_fclk_location_id.id or False, }) diff --git a/fusion_clock/controllers/portal_clock.py b/fusion_clock/controllers/portal_clock.py index fc209af6..a6876ea7 100644 --- a/fusion_clock/controllers/portal_clock.py +++ b/fusion_clock/controllers/portal_clock.py @@ -9,7 +9,7 @@ from datetime import datetime, timedelta from odoo import http, fields, _ from odoo.http import request from odoo.addons.portal.controllers.portal import CustomerPortal -from odoo.addons.fusion_clock.models.tz_utils import get_local_today, get_local_day_boundaries +from odoo.addons.fusion_clock.models.tz_utils import get_local_today, get_local_day_boundaries, utc_to_local_str _logger = logging.getLogger(__name__) @@ -119,11 +119,20 @@ class FusionClockPortal(CustomerPortal): ]) week_hours = sum(a.x_fclk_net_hours or 0 for a in week_atts) - # Recent activity - recent = request.env['hr.attendance'].sudo().search([ + # Recent activity with local-timezone formatted times + recent_raw = request.env['hr.attendance'].sudo().search([ ('employee_id', '=', employee.id), ('check_out', '!=', False), ], order='check_in desc', limit=10) + recent = [] + for att in recent_raw: + recent.append({ + 'att': att, + 'day_name': utc_to_local_str(att.check_in, request.env, employee, '%a'), + 'day_num': utc_to_local_str(att.check_in, request.env, employee, '%d'), + 'time_in': utc_to_local_str(att.check_in, request.env, employee, '%I:%M %p'), + 'time_out': utc_to_local_str(att.check_out, request.env, employee, '%I:%M %p') if att.check_out else '--', + }) # Prepare locations JSON for JS locations_json = json.dumps([{ @@ -203,9 +212,19 @@ class FusionClockPortal(CustomerPortal): net_hours = sum(a.x_fclk_net_hours or 0 for a in attendances if a.check_out) total_breaks = sum(a.x_fclk_break_minutes or 0 for a in attendances if a.check_out) + ts_attendances = [] + for att in attendances: + ts_attendances.append({ + 'att': att, + 'day_name': utc_to_local_str(att.check_in, request.env, employee, '%a'), + 'day_date': utc_to_local_str(att.check_in, request.env, employee, '%b %d'), + 'time_in': utc_to_local_str(att.check_in, request.env, employee, '%I:%M %p'), + 'time_out': utc_to_local_str(att.check_out, request.env, employee, '%I:%M %p') if att.check_out else '', + }) + values = { 'employee': employee, - 'attendances': attendances, + 'attendances': ts_attendances, 'period_start': period_start, 'period_end': period_end, 'period': period, diff --git a/fusion_clock/static/src/js/fusion_clock_portal.js b/fusion_clock/static/src/js/fusion_clock_portal.js index 3eceede7..c570eba6 100644 --- a/fusion_clock/static/src/js/fusion_clock_portal.js +++ b/fusion_clock/static/src/js/fusion_clock_portal.js @@ -39,6 +39,7 @@ export class FusionClockPortal extends Interaction { } if (this.locations.length > 0) { this.selectedLocationId = this.locations[0].id; + this._restoreSelectedLocation(); } // Start live clock @@ -50,7 +51,15 @@ export class FusionClockPortal extends Interaction { this._startTimer(); this._updateUIForClockIn({ location_name: this.el.dataset.locationName || "", + location_address: this.el.dataset.locationAddress || "", }); + const locId = parseInt(this.el.dataset.locationId || "0"); + if (locId) { + this.selectedLocationId = locId; + this._saveSelectedLocation(locId); + } + } else if (this.locations.length > 1) { + this._detectNearestLocation(); } this._updateDateDisplay(); @@ -121,7 +130,9 @@ export class FusionClockPortal extends Interaction { document.querySelectorAll(".fclk-modal-item").forEach((item) => { item.addEventListener("click", () => { - this.selectedLocationId = parseInt(item.dataset.id); + const locId = parseInt(item.dataset.id); + this.selectedLocationId = locId; + this._saveSelectedLocation(locId); const nameEl = document.getElementById("fclk-location-name"); const addrEl = document.getElementById("fclk-location-address"); if (nameEl) nameEl.textContent = item.dataset.name; @@ -132,6 +143,60 @@ export class FusionClockPortal extends Interaction { }); } + // ========================================================================= + // Nearest Location Detection + // ========================================================================= + + _detectNearestLocation() { + if (!navigator.geolocation) return; + navigator.geolocation.getCurrentPosition( + (pos) => this._selectNearestFromCoords(pos.coords.latitude, pos.coords.longitude), + async () => { + try { + const resp = await fetch("https://ipapi.co/json/"); + if (resp.ok) { + const data = await resp.json(); + if (data.latitude && data.longitude) { + this._selectNearestFromCoords(data.latitude, data.longitude); + } + } + } catch { /* silent */ } + }, + { enableHighAccuracy: true, timeout: 10000, maximumAge: 60000 } + ); + } + + _selectNearestFromCoords(lat, lng) { + let nearest = null; + let minDist = Infinity; + for (const loc of this.locations) { + if (!loc.latitude || !loc.longitude) continue; + const dist = this._haversine(lat, lng, loc.latitude, loc.longitude); + if (dist < minDist) { + minDist = dist; + nearest = loc; + } + } + if (!nearest) return; + this.selectedLocationId = nearest.id; + const nameEl = document.getElementById("fclk-location-name"); + const addrEl = document.getElementById("fclk-location-address"); + if (nameEl) nameEl.textContent = nearest.name; + if (addrEl) addrEl.textContent = nearest.address || ""; + } + + _haversine(lat1, lon1, lat2, lon2) { + const R = 6371000; + const toRad = (v) => (v * Math.PI) / 180; + const dLat = toRad(lat2 - lat1); + const dLon = toRad(lon2 - lon1); + const a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + } + // ========================================================================= // Clock Action // ========================================================================= @@ -262,6 +327,7 @@ export class FusionClockPortal extends Interaction { this._playSound("in"); this._showToast(result.message, "success"); this._saveState(); + if (this.selectedLocationId) this._saveSelectedLocation(this.selectedLocationId); } else if (result.action === "clock_out") { this.isCheckedIn = false; this._updateUIForClockOut(result); @@ -302,6 +368,10 @@ export class FusionClockPortal extends Interaction { const locEl = document.getElementById("fclk-location-name"); if (locEl) locEl.textContent = data.location_name; } + if (data.location_address) { + const addrEl = document.getElementById("fclk-location-address"); + if (addrEl) addrEl.textContent = data.location_address; + } } _updateUIForClockOut(data) { @@ -522,6 +592,27 @@ export class FusionClockPortal extends Interaction { } catch (e) {} } + _saveSelectedLocation(locId) { + try { + localStorage.setItem("fclk_selected_location", String(locId)); + } catch (e) {} + } + + _restoreSelectedLocation() { + try { + const saved = localStorage.getItem("fclk_selected_location"); + if (!saved) return; + const savedId = parseInt(saved); + const loc = this.locations.find((l) => l.id === savedId); + if (!loc) return; + this.selectedLocationId = savedId; + const nameEl = document.getElementById("fclk-location-name"); + const addrEl = document.getElementById("fclk-location-address"); + if (nameEl) nameEl.textContent = loc.name; + if (addrEl) addrEl.textContent = loc.address || ""; + } catch (e) {} + } + // ========================================================================= // Reason Modal & Leave Request // ========================================================================= @@ -607,7 +698,7 @@ export class FusionClockPortal extends Interaction { if (result.is_checked_in && !this.isCheckedIn) { this.isCheckedIn = true; this.checkInTime = new Date(result.check_in + "Z"); - this._updateUIForClockIn({ location_name: result.location_name }); + this._updateUIForClockIn({ location_name: result.location_name, location_address: result.location_address || "" }); this._startTimer(); this._saveState(); } else if (result.is_checked_in && this.isCheckedIn) { diff --git a/fusion_clock/views/portal_clock_templates.xml b/fusion_clock/views/portal_clock_templates.xml index d84c58cb..a5060f2f 100644 --- a/fusion_clock/views/portal_clock_templates.xml +++ b/fusion_clock/views/portal_clock_templates.xml @@ -85,6 +85,8 @@ t-att-data-checked-in="'true' if is_checked_in else 'false'" t-att-data-check-in-time="current_attendance.check_in.isoformat() if current_attendance and current_attendance.check_in else ''" t-att-data-location-name="current_attendance.x_fclk_location_id.name if current_attendance and current_attendance.x_fclk_location_id else ''" + t-att-data-location-address="current_attendance.x_fclk_location_id.address if current_attendance and current_attendance.x_fclk_location_id else ''" + t-att-data-location-id="current_attendance.x_fclk_location_id.id if current_attendance and current_attendance.x_fclk_location_id else '0'" t-att-data-enable-sounds="'true' if enable_sounds else 'false'" t-att-data-google-maps-key="google_maps_key or ''"> @@ -222,27 +224,27 @@ View All
- +
- +
- +
- +
- - - + + -
- h + h
diff --git a/fusion_clock/views/portal_timesheet_templates.xml b/fusion_clock/views/portal_timesheet_templates.xml index fff9a016..3f17deaf 100644 --- a/fusion_clock/views/portal_timesheet_templates.xml +++ b/fusion_clock/views/portal_timesheet_templates.xml @@ -66,19 +66,19 @@ - + - + - + - + - - - + + + AUTO @@ -86,18 +86,18 @@ Active - m + m - h + h - + Correct diff --git a/fusion_schedule/controllers/portal_schedule.py b/fusion_schedule/controllers/portal_schedule.py index bf01ba76..4b2db39b 100644 --- a/fusion_schedule/controllers/portal_schedule.py +++ b/fusion_schedule/controllers/portal_schedule.py @@ -1213,7 +1213,7 @@ class PortalSchedule(CustomerPortal): 'response_type': 'code', 'scope': MICROSOFT_SCOPES, 'response_mode': 'query', - 'prompt': 'consent', + 'prompt': 'select_account', 'state': state, } diff --git a/fusion_tasks/static/src/css/fusion_task_map_view.scss b/fusion_tasks/static/src/css/fusion_task_map_view.scss index 31c3d696..f3b9b097 100644 --- a/fusion_tasks/static/src/css/fusion_task_map_view.scss +++ b/fusion_tasks/static/src/css/fusion_task_map_view.scss @@ -343,6 +343,14 @@ $transition-speed: .25s; .fa { opacity: .5; } } +.fc_task_date { + font-size: 11px; + color: #6366f1; + font-weight: 600; + margin-bottom: 3px; + .fa { opacity: .5; } +} + .fc_task_detail { font-size: 11px; color: $body-color; diff --git a/fusion_tasks/static/src/js/fusion_task_map_view.js b/fusion_tasks/static/src/js/fusion_task_map_view.js index c575e123..38bec401 100644 --- a/fusion_tasks/static/src/js/fusion_task_map_view.js +++ b/fusion_tasks/static/src/js/fusion_task_map_view.js @@ -241,6 +241,9 @@ function groupTasks(tasksData, localInstanceId, visibleTechIds) { const src = task.x_fc_sync_source || localInstanceId || ""; task._sourceLabel = src ? src.charAt(0).toUpperCase() + src.slice(1) : ""; task._sourceColor = SOURCE_COLORS[src] || "#6c757d"; + task._dateLabel = task.scheduled_date + ? new Date(task.scheduled_date + "T12:00:00").toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' }) + : "No date"; task._hasCoords = task.address_lat && task.address_lng && task.address_lat !== 0 && task.address_lng !== 0; groups[g].tasks.push(task); groups[g].count++; diff --git a/fusion_tasks/static/src/xml/fusion_task_map_view.xml b/fusion_tasks/static/src/xml/fusion_task_map_view.xml index f799ba5d..7e99b8fe 100644 --- a/fusion_tasks/static/src/xml/fusion_task_map_view.xml +++ b/fusion_tasks/static/src/xml/fusion_task_map_view.xml @@ -109,6 +109,11 @@
+ +
+ +
+