-
Notifications
You must be signed in to change notification settings - Fork 4
Timezone aware #372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Timezone aware #372
Changes from all commits
2f5f2ac
8c2b15a
3f5ec1b
5a08bb2
1db30ac
36dc1b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { addDays, differenceInCalendarDays, format } from 'date-fns'; | ||
| import { Moment } from 'moment-timezone'; | ||
|
|
||
| export const fillUntilTargetDate = ( | ||
| data: { date: string; started: number; completed: number }[], | ||
|
|
@@ -24,16 +25,16 @@ export const fillUntilTargetDate = ( | |
|
|
||
| export const fillMissingDays = ( | ||
| data: { date: string; started: number; completed: number }[], | ||
| baseDate: Date, | ||
| baseDate: Moment, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify all callers of fillMissingDays have been updated. The function signature changed from Run the following script to find all usages of 🏁 Script executed: #!/bin/bash
# Description: Find all usages of fillMissingDays to verify they pass Moment instances
# Search for function calls with context
rg -n -C5 'fillMissingDays\s*\(' --type=tsLength of output: 6253 Convert baseDate/startDate to Moment in fillMissingDays calls 🤖 Prompt for AI Agents |
||
| totalDays = 7, | ||
| ) => { | ||
| const filledData: { date: string; started: number; completed: number }[] = []; | ||
|
|
||
| const mapDateToData = new Map(data.map((item) => [item.date, item])); | ||
|
|
||
| for (let i = 0; i < totalDays; i++) { | ||
| const date = addDays(baseDate, i); | ||
| const key = format(date, 'yyyy-MM-dd'); | ||
| const date = baseDate.clone().add(i, 'days'); | ||
| const key = date.format('YYYY-MM-DD'); | ||
| const item = mapDateToData.get(key); | ||
|
|
||
| if (item) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,11 @@ | ||
| import { Job } from 'bullmq'; | ||
| import { endOfDay } from 'date-fns'; // эсвэл өөр utility | ||
| import { | ||
| getEnv, | ||
| getSaasOrganizations, | ||
| sendTRPCMessage, | ||
| sendWorkerQueue, | ||
| } from 'erxes-api-shared/utils'; | ||
| import { tz } from 'moment-timezone'; | ||
| import { generateModels } from '~/connectionResolvers'; | ||
|
Comment on lines
+5
to
9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Import moment-timezone correctly in the worker
Apply this diff: -import {
- getEnv,
- getSaasOrganizations,
- sendTRPCMessage,
- sendWorkerQueue,
-} from 'erxes-api-shared/utils';
-import { tz } from 'moment-timezone';
+import {
+ getEnv,
+ getSaasOrganizations,
+ sendTRPCMessage,
+ sendWorkerQueue,
+} from 'erxes-api-shared/utils';
+import moment from 'moment-timezone';
@@
- const tzToday = tz(new Date(), timezone);
+ const tzToday = moment.tz(new Date(), timezone);
@@
- const utcStart = tzToday.startOf('day').toDate();
- const utcEnd = tzToday.endOf('day').toDate();
+ const utcStart = tzToday.clone().startOf('day').toDate();
+ const utcEnd = tzToday.clone().endOf('day').toDate();Also applies to: 48-58 🤖 Prompt for AI Agents |
||
|
|
||
| export const dailyCheckCycles = async () => { | ||
|
|
@@ -14,35 +15,51 @@ export const dailyCheckCycles = async () => { | |
| const orgs = await getSaasOrganizations(); | ||
|
|
||
| for (const org of orgs) { | ||
| if (org.enabledcycles) { | ||
| sendWorkerQueue('operations', 'checkCycle').add('checkCycle', { | ||
| subdomain: org.subdomain, | ||
| }); | ||
| } | ||
| sendWorkerQueue('operations', 'checkCycle').add('checkCycle', { | ||
| subdomain: org.subdomain, | ||
| timezone: org.timezone, | ||
| }); | ||
| } | ||
|
|
||
| return 'success'; | ||
| } else { | ||
| const timezone = await sendTRPCMessage({ | ||
| pluginName: 'core', | ||
| method: 'query', | ||
| module: 'configs', | ||
| action: 'getConfig', | ||
| input: { | ||
| code: 'TIMEZONE', | ||
| }, | ||
| defaultValue: 'UTC', | ||
| }); | ||
|
|
||
| sendWorkerQueue('operations', 'checkCycle').add('checkCycle', { | ||
| subdomain: 'os', | ||
| timezone, | ||
| }); | ||
| return 'success'; | ||
| } | ||
| }; | ||
|
|
||
| export const checkCycle = async (job: Job) => { | ||
| const { subdomain } = job?.data ?? {}; | ||
| const { subdomain, timezone = 'UTC' } = job?.data ?? {}; | ||
|
|
||
| const tzToday = tz(new Date(), timezone); | ||
|
|
||
| if (tzToday.hour() !== 0) { | ||
| return; | ||
| } | ||
|
|
||
| const models = await generateModels(subdomain); | ||
|
|
||
| const today = new Date(); | ||
| const utcStart = tzToday.startOf('day').toDate(); | ||
| const utcEnd = tzToday.endOf('day').toDate(); | ||
|
|
||
| const endCycles = await models.Cycle.find({ | ||
| isActive: true, | ||
| isCompleted: false, | ||
| endDate: { | ||
| $lte: endOfDay(today), | ||
| }, | ||
| endDate: { $gte: utcStart, $lte: utcEnd }, | ||
| }); | ||
|
|
||
| if (endCycles?.length) { | ||
|
|
@@ -54,9 +71,7 @@ export const checkCycle = async (job: Job) => { | |
| const startCycles = await models.Cycle.find({ | ||
| isActive: false, | ||
| isCompleted: false, | ||
| startDate: { | ||
| $lte: endOfDay(today), | ||
| }, | ||
| startDate: { $gte: utcStart, $lte: utcEnd }, | ||
| }); | ||
|
|
||
| if (startCycles?.length) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,7 @@ export const initMQWorkers = async (redis: any) => { | |
| await myQueue.upsertJobScheduler( | ||
| 'operations-daily-cycles-check', | ||
| { | ||
| pattern: '0 0 * * *', | ||
| pattern: '0 * * * *', | ||
| tz: 'UTC', | ||
|
Comment on lines
+13
to
14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 🧩 Analysis chainVerify the worker has timezone-aware guards to prevent excessive executions. The cron schedule changed from daily at midnight ( Run the following script to examine the worker implementation: 🏁 Script executed: #!/bin/bash
# Description: Verify dailyCheckCycles has timezone-aware execution guards
# Find the dailyCheckCycles implementation
ast-grep --pattern $'export $_ dailyCheckCycles = $_'
# Search for timezone checking logic
rg -n -C10 'dailyCheckCycles' backend/plugins/operation_api/src/worker/dailyCheckCycles.tsLength of output: 649 Add timezone-based execution guard in dailyCheckCycles |
||
| }, | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,16 +1,17 @@ | ||||||||||||||||||||||||||||||||||
| import { useEffect } from 'react'; | ||||||||||||||||||||||||||||||||||
| import { useSwitchLanguage } from '~/i18n'; | ||||||||||||||||||||||||||||||||||
| import { SubmitHandler } from 'react-hook-form'; | ||||||||||||||||||||||||||||||||||
| import { Button, Form, Spinner, useToast } from 'erxes-ui'; | ||||||||||||||||||||||||||||||||||
| import { useConfig } from '@/settings/file-upload/hook/useConfigs'; | ||||||||||||||||||||||||||||||||||
| import { SelectCurrency } from '@/settings/general/components/SelectCurrency'; | ||||||||||||||||||||||||||||||||||
| import { TGeneralSettingsProps } from '@/settings/general/types'; | ||||||||||||||||||||||||||||||||||
| import { TConfig } from '@/settings/file-upload/types'; | ||||||||||||||||||||||||||||||||||
| import { GeneralSettingsSkeleton } from '@/settings/general/components/GeneralSettingsSkeleton'; | ||||||||||||||||||||||||||||||||||
| import { useGeneralSettingsForms } from '@/settings/general/hooks/useGeneralSettingsForms'; | ||||||||||||||||||||||||||||||||||
| import SelectControl from '@/settings/general/components/SelectControl'; | ||||||||||||||||||||||||||||||||||
| import { LANGUAGES } from '@/settings/general/constants/data'; | ||||||||||||||||||||||||||||||||||
| import { SelectCurrency } from '@/settings/general/components/SelectCurrency'; | ||||||||||||||||||||||||||||||||||
| import { SelectMainCurrency } from '@/settings/general/components/SelectMainCurrency'; | ||||||||||||||||||||||||||||||||||
| import { SelectTimezone } from '@/settings/general/components/SelectTimezone'; | ||||||||||||||||||||||||||||||||||
| import { LANGUAGES } from '@/settings/general/constants/data'; | ||||||||||||||||||||||||||||||||||
| import { useGeneralSettingsForms } from '@/settings/general/hooks/useGeneralSettingsForms'; | ||||||||||||||||||||||||||||||||||
| import { TGeneralSettingsProps } from '@/settings/general/types'; | ||||||||||||||||||||||||||||||||||
| import { Button, Form, Spinner, useToast } from 'erxes-ui'; | ||||||||||||||||||||||||||||||||||
| import { useEffect } from 'react'; | ||||||||||||||||||||||||||||||||||
| import { SubmitHandler } from 'react-hook-form'; | ||||||||||||||||||||||||||||||||||
| import { useSwitchLanguage } from '~/i18n'; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const GeneralSettings = () => { | ||||||||||||||||||||||||||||||||||
| const { languages } = useSwitchLanguage(); | ||||||||||||||||||||||||||||||||||
|
|
@@ -23,19 +24,22 @@ const GeneralSettings = () => { | |||||||||||||||||||||||||||||||||
| const { configs, updateConfig, loading, isLoading } = useConfig(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const updateCurrency = (data: TGeneralSettingsProps) => { | ||||||||||||||||||||||||||||||||||
| const updatedConfigs = configs.reduce( | ||||||||||||||||||||||||||||||||||
| (acc: Record<string, any>, config: TConfig) => { | ||||||||||||||||||||||||||||||||||
| const key = config.code as keyof TGeneralSettingsProps; | ||||||||||||||||||||||||||||||||||
| acc[config.code] = key in data ? data[key] : config.value; | ||||||||||||||||||||||||||||||||||
| const updatedConfigs = { | ||||||||||||||||||||||||||||||||||
| // start with all existing configs | ||||||||||||||||||||||||||||||||||
| ...configs.reduce((acc: Record<string, any>, config: TConfig) => { | ||||||||||||||||||||||||||||||||||
| acc[config.code] = config.value; | ||||||||||||||||||||||||||||||||||
| return acc; | ||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||
| {} as Record<string, any>, | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| }, {} as Record<string, any>), | ||||||||||||||||||||||||||||||||||
| // override/add with new data | ||||||||||||||||||||||||||||||||||
| ...data, | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| updateConfig(updatedConfigs); | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const submitHandler: SubmitHandler<TGeneralSettingsProps> = (data) => { | ||||||||||||||||||||||||||||||||||
| updateCurrency(data); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| handleLanguage(data.languageCode).then(() => { | ||||||||||||||||||||||||||||||||||
| toast({ | ||||||||||||||||||||||||||||||||||
| title: 'Updated successfully', | ||||||||||||||||||||||||||||||||||
|
|
@@ -52,8 +56,13 @@ const GeneralSettings = () => { | |||||||||||||||||||||||||||||||||
| const mainCurrency = configs?.find( | ||||||||||||||||||||||||||||||||||
| (data: any) => data.code === 'mainCurrency', | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const timezone = configs?.find((data: any) => data.code === 'TIMEZONE'); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| methods.setValue('dealCurrency', currencies?.value); | ||||||||||||||||||||||||||||||||||
| methods.setValue('mainCurrency', mainCurrency?.value); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| timezone && methods.setValue('TIMEZONE', timezone?.value); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+60
to
66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolve ESLint failure when hydrating TIMEZONE Linting fails on Line 65 ( Apply this diff: - const timezone = configs?.find((data: any) => data.code === 'TIMEZONE');
-
- methods.setValue('dealCurrency', currencies?.value);
- methods.setValue('mainCurrency', mainCurrency?.value);
-
- timezone && methods.setValue('TIMEZONE', timezone?.value);
+ const timezone = configs?.find((data: any) => data.code === 'TIMEZONE');
+
+ methods.setValue('dealCurrency', currencies?.value);
+ methods.setValue('mainCurrency', mainCurrency?.value);
+
+ if (timezone) {
+ methods.setValue('TIMEZONE', timezone.value);
+ }📝 Committable suggestion
Suggested change
🧰 Tools🪛 ESLint[error] 65-65: Expected an assignment or function call and instead saw an expression. (@typescript-eslint/no-unused-expressions) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| }, [configs, methods]); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
@@ -78,6 +87,7 @@ const GeneralSettings = () => { | |||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||
| <SelectMainCurrency /> | ||||||||||||||||||||||||||||||||||
| <SelectCurrency /> | ||||||||||||||||||||||||||||||||||
| <SelectTimezone /> | ||||||||||||||||||||||||||||||||||
| <Button disabled={isLoading} type="submit" className="w-1/4 ml-auto"> | ||||||||||||||||||||||||||||||||||
| {isLoading ? ( | ||||||||||||||||||||||||||||||||||
| <Spinner className="stroke-white/90 w-4 h-4" /> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { Form, TimezoneSelect } from 'erxes-ui'; | ||
| import { useFormContext } from 'react-hook-form'; | ||
| import { TGeneralSettingsProps } from '../types'; | ||
|
|
||
| export function SelectTimezone() { | ||
| const form = useFormContext<TGeneralSettingsProps>(); | ||
|
|
||
| return ( | ||
| <Form.Field | ||
| control={form.control} | ||
| name="TIMEZONE" | ||
| render={({ field }) => ( | ||
| <Form.Item> | ||
| <Form.Label>Timezone</Form.Label> | ||
| <Form.Control> | ||
| <TimezoneSelect | ||
| value={field.value} | ||
| onValueChange={field.onChange} | ||
| /> | ||
| </Form.Control> | ||
| </Form.Item> | ||
| )} | ||
| /> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix Moment Timezone import to avoid compilation failure
moment-timezonedoesn’t exposetzas a named export, so the build breaks (TypeScript reports “Module 'moment-timezone' has no exported member 'tz'”). Switch to the default moment import and callmoment.tz(...)instead on Line 253 and Line 254.Apply this diff:
Also applies to: 253-259
🤖 Prompt for AI Agents