@@ -17,10 +17,10 @@ private function query(): Builder
1717 public function getProjects (int $ startId , int $ limit , ?int $ modifiedAfter = null , ?array $ ids = null ): array
1818 {
1919 return $ this ->query ()
20- ->select (["id " , "name " , " modified " ])
20+ ->select (["project. id " , "project. name " , $ this -> modifiedSelect ( " project " ) ])
2121 ->from ("zp_projects " , "project " )
2222 ->where ("project.id " , ">= " , $ startId )
23- ->when ($ modifiedAfter !== null , fn ($ query ) => $ query ->where ("project.modified " , ">= " , CarbonImmutable:: createFromTimestamp ( $ modifiedAfter )-> format (APIData:: DATE_FORMAT )))
23+ ->when ($ modifiedAfter !== null , fn ($ query ) => $ query ->where ($ this -> modified ( "project " ) , ">= " , $ this -> cutoff ( $ modifiedAfter )))
2424 ->when ($ ids !== null , fn ($ query ) => $ query ->whereIn ("project.id " , $ ids ))
2525 ->orderBy ("id " , "ASC " )
2626 ->limit ($ limit )
@@ -31,11 +31,11 @@ public function getProjects(int $startId, int $limit, ?int $modifiedAfter = null
3131 public function getMilestones (int $ startId , int $ limit , ?int $ modifiedAfter = null , ?array $ ids = null , ?array $ projectIds = null ): array
3232 {
3333 return $ this ->query ()
34- ->select (["id " , "headline " , "projectId " , " modified " ])
34+ ->select (["ticket. id " , "ticket. headline " , "ticket. projectId " , $ this -> modifiedSelect ( " ticket " ) ])
3535 ->from ("zp_tickets " , "ticket " )
3636 ->where ("ticket.id " , ">= " , $ startId )
3737 ->where ("ticket.type " , "= " , "milestone " )
38- ->when ($ modifiedAfter !== null , fn ($ query ) => $ query ->where ("ticket.date " , ">= " , CarbonImmutable:: createFromTimestamp ( $ modifiedAfter )-> format (APIData:: DATE_FORMAT )))
38+ ->when ($ modifiedAfter !== null , fn ($ query ) => $ query ->where ($ this -> modified ( "ticket " ) , ">= " , $ this -> cutoff ( $ modifiedAfter )))
3939 ->when ($ ids !== null , fn ($ query ) => $ query ->whereIn ("ticket.id " , $ ids ))
4040 ->when ($ projectIds !== null , fn ($ query ) => $ query ->whereIn ("ticket.projectId " , $ projectIds ))
4141 ->orderBy ("id " , "ASC " )
@@ -47,12 +47,12 @@ public function getMilestones(int $startId, int $limit, ?int $modifiedAfter = nu
4747 public function getTickets (int $ startId , int $ limit , ?int $ modifiedAfter = null , ?array $ ids = null , ?array $ projectIds = null ): array
4848 {
4949 return $ this ->query ()
50- ->select (["ticket.id " , "ticket.headline " , "ticket.projectId " , "ticket.status " , "ticket.planHours " , "ticket.hourRemaining " , "ticket.tags " , "ticket.dateToFinish " , "ticket.editTo " , "ticket.milestoneid " , "ticket.modified " , "user.username " ])
50+ ->select (["ticket.id " , "ticket.headline " , "ticket.projectId " , "ticket.status " , "ticket.planHours " , "ticket.hourRemaining " , "ticket.tags " , "ticket.dateToFinish " , "ticket.editTo " , "ticket.milestoneid " , $ this -> modifiedSelect ( "ticket " ) , "user.username " ])
5151 ->from ("zp_tickets " , "ticket " )
5252 ->where ("ticket.id " , ">= " , $ startId )
5353 ->where ("ticket.type " , "<> " , "milestone " )
5454 ->leftJoin ('zp_user as user ' , "user.id " , "= " , "ticket.editorId " )
55- ->when ($ modifiedAfter !== null , fn ($ query ) => $ query ->where ("ticket.date " , ">= " , CarbonImmutable:: createFromTimestamp ( $ modifiedAfter )-> format (APIData:: DATE_FORMAT )))
55+ ->when ($ modifiedAfter !== null , fn ($ query ) => $ query ->where ($ this -> modified ( "ticket " ) , ">= " , $ this -> cutoff ( $ modifiedAfter )))
5656 ->when ($ ids !== null , fn ($ query ) => $ query ->whereIn ("ticket.id " , $ ids ))
5757 ->when ($ projectIds !== null , fn ($ query ) => $ query ->whereIn ("ticket.projectId " , $ projectIds ))
5858 ->orderBy ("id " , "ASC " )
@@ -65,12 +65,12 @@ public function getTimesheets(int $startId, int $limit, ?int $modifiedAfter = nu
6565 {
6666 return $ this ->query ()
6767 ->from ("zp_timesheets " , "timesheet " )
68- ->select (["timesheet.id " , "timesheet.description " , "timesheet.hours " , "timesheet.workDate " , "timesheet.modified " , "timesheet.ticketId " , "timesheet.userId " , "timesheet.kind " , "user.username " , "ticket.projectId " ])
68+ ->select (["timesheet.id " , "timesheet.description " , "timesheet.hours " , "timesheet.workDate " , $ this -> modifiedSelect ( "timesheet " ) , "timesheet.ticketId " , "timesheet.userId " , "timesheet.kind " , "user.username " , "ticket.projectId " ])
6969 ->where ("timesheet.id " , ">= " , $ startId )
7070 ->whereNotNull ("timesheet.hours " )
7171 ->leftJoin ('zp_user as user ' , "user.id " , "= " , "timesheet.userId " )
7272 ->leftJoin ('zp_tickets as ticket ' , "ticket.id " , "= " , "timesheet.ticketId " )
73- ->when ($ modifiedAfter !== null , fn ($ query ) => $ query ->where ("timesheet.modified " , ">= " , CarbonImmutable:: createFromTimestamp ( $ modifiedAfter )-> format (APIData:: DATE_FORMAT )))
73+ ->when ($ modifiedAfter !== null , fn ($ query ) => $ query ->where ($ this -> modified ( "timesheet " ) , ">= " , $ this -> cutoff ( $ modifiedAfter )))
7474 ->when ($ ids !== null , fn ($ query ) => $ query ->whereIn ("timesheet.id " , $ ids ))
7575 ->when ($ projectIds !== null , fn ($ query ) => $ query ->whereIn ("ticket.projectId " , $ projectIds ))
7676 ->orderBy ("timesheet.id " , "ASC " )
@@ -86,10 +86,10 @@ public function getWorkers(int $startId, int $limit, ?int $modifiedAfter = null,
8686 // CONCAT_WS skips a missing name part, so a worker with only a
8787 // firstname keeps a usable name. NULLIF turns an all-blank name into
8888 // null rather than a string of whitespace.
89- ->select (["worker.id " , "worker.username " , DB ::raw ("NULLIF(TRIM(CONCAT_WS(' ', worker.firstname, worker.lastname)), '') as name " )])
89+ ->select (["worker.id " , "worker.username " , DB ::raw ("NULLIF(TRIM(CONCAT_WS(' ', worker.firstname, worker.lastname)), '') as name " ), $ this -> modifiedSelect ( " worker " ) ])
9090 ->where ("worker.id " , ">= " , $ startId )
9191 ->where ("worker.source " , "<> " , "api " )
92- ->when ($ modifiedAfter !== null , fn ($ query ) => $ query ->where ("worker.modified " , ">= " , CarbonImmutable:: createFromTimestamp ( $ modifiedAfter )-> format (APIData:: DATE_FORMAT )))
92+ ->when ($ modifiedAfter !== null , fn ($ query ) => $ query ->where ($ this -> modified ( "worker " ) , ">= " , $ this -> cutoff ( $ modifiedAfter )))
9393 ->when ($ ids !== null , fn ($ query ) => $ query ->whereIn ("worker.id " , $ ids ))
9494 ->orderBy ("worker.id " , "ASC " )
9595 ->limit ($ limit )
@@ -111,8 +111,34 @@ public function getDeleted(string $type, ?int $deletedAfter = null): array
111111 ->select (["entryId " , "dateDeleted " ])
112112 ->when ($ type === APIData::TYPE_MILESTONES , fn ($ query ) => $ query ->where ('type ' , '= ' , 'milestone ' ))
113113 ->when ($ type === APIData::TYPE_TICKETS , fn ($ query ) => $ query ->where ('type ' , '<> ' , 'milestone ' ))
114- ->when ($ deletedAfter !== null , fn ($ query ) => $ query ->where ("entry.dateDeleted " , ">= " , CarbonImmutable:: createFromTimestamp ( $ deletedAfter )-> format (APIData:: DATE_FORMAT )))
114+ ->when ($ deletedAfter !== null , fn ($ query ) => $ query ->where ("entry.dateDeleted " , ">= " , $ this -> cutoff ( $ deletedAfter )))
115115 ->get ()
116116 ->toArray ();
117117 }
118+
119+ /**
120+ * The plugin-owned timestamp column, qualified by the query's table alias.
121+ * Core's own `modified` is not maintained on every write path, so it cannot
122+ * carry the modifiedAfter contract — see SchemaRepository.
123+ */
124+ private function modified (string $ alias ): string
125+ {
126+ return sprintf ('%s.%s ' , $ alias , SchemaRepository::COLUMN );
127+ }
128+
129+ /**
130+ * Exposed to consumers as plain `modified`, so the column swap is invisible
131+ * to them and to the mapping in APIData.
132+ */
133+ private function modifiedSelect (string $ alias ): string
134+ {
135+ return sprintf ('%s as modified ' , $ this ->modified ($ alias ));
136+ }
137+
138+ private function cutoff (int $ timestamp ): string
139+ {
140+ // Explicit UTC: Carbon 3 defaults to it, but Carbon comes from the host
141+ // Leantime install, and the triggers write UTC_TIMESTAMP().
142+ return CarbonImmutable::createFromTimestamp ($ timestamp , 'UTC ' )->format (APIData::DATE_FORMAT );
143+ }
118144}
0 commit comments