scheduled transaction supports start time and end time (#36)

This commit is contained in:
MaysWind
2025-02-28 00:14:52 +08:00
parent d769e833e7
commit 377a4899b7
22 changed files with 500 additions and 17 deletions
+64 -3
View File
@@ -156,7 +156,12 @@ func (a *TransactionTemplatesApi) TemplateCreateHandler(c *core.WebContext) (any
} }
serverUtcOffset := utils.GetServerTimezoneOffsetMinutes() serverUtcOffset := utils.GetServerTimezoneOffsetMinutes()
template := a.createNewTemplateModel(uid, &templateCreateReq, maxOrderId+1) template, err := a.createNewTemplateModel(uid, &templateCreateReq, maxOrderId+1)
if err != nil {
log.Errorf(c, "[transaction_templates.TemplateCreateHandler] failed to create new template for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
if a.CurrentConfig().EnableDuplicateSubmissionsCheck && templateCreateReq.ClientSessionId != "" { if a.CurrentConfig().EnableDuplicateSubmissionsCheck && templateCreateReq.ClientSessionId != "" {
found, remark := a.GetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE, uid, templateCreateReq.ClientSessionId) found, remark := a.GetSubmissionRemark(duplicatechecker.DUPLICATE_CHECKER_TYPE_NEW_TEMPLATE, uid, templateCreateReq.ClientSessionId)
@@ -260,6 +265,34 @@ func (a *TransactionTemplatesApi) TemplateModifyHandler(c *core.WebContext) (any
newTemplate.ScheduledFrequency = a.getOrderedFrequencyValues(*templateModifyReq.ScheduledFrequency) newTemplate.ScheduledFrequency = a.getOrderedFrequencyValues(*templateModifyReq.ScheduledFrequency)
newTemplate.ScheduledAt = a.getUTCScheduledAt(*templateModifyReq.ScheduledTimezoneUtcOffset) newTemplate.ScheduledAt = a.getUTCScheduledAt(*templateModifyReq.ScheduledTimezoneUtcOffset)
newTemplate.ScheduledTimezoneUtcOffset = *templateModifyReq.ScheduledTimezoneUtcOffset newTemplate.ScheduledTimezoneUtcOffset = *templateModifyReq.ScheduledTimezoneUtcOffset
if templateModifyReq.ScheduledStartDate != nil {
startTime, err := utils.ParseFromLongDateFirstTime(*templateModifyReq.ScheduledStartDate, *templateModifyReq.ScheduledTimezoneUtcOffset)
if err != nil {
log.Errorf(c, "[transaction_templates.TemplateModifyHandler] failed to parse scheduled start date for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
startUnixTime := startTime.Unix()
newTemplate.ScheduledStartTime = &startUnixTime
}
if templateModifyReq.ScheduledEndDate != nil {
endTime, err := utils.ParseFromLongDateLastTime(*templateModifyReq.ScheduledEndDate, *templateModifyReq.ScheduledTimezoneUtcOffset)
if err != nil {
log.Errorf(c, "[transaction_templates.TemplateModifyHandler] failed to parse scheduled end date for user \"uid:%d\", because %s", uid, err.Error())
return nil, errs.Or(err, errs.ErrOperationFailed)
}
endUnixTime := endTime.Unix()
newTemplate.ScheduledEndTime = &endUnixTime
}
if newTemplate.ScheduledStartTime != nil && newTemplate.ScheduledEndTime != nil && *newTemplate.ScheduledStartTime > *newTemplate.ScheduledEndTime {
return nil, errs.ErrScheduledTransactionTemplateStartDataLaterThanEndDate
}
} }
if newTemplate.Name == template.Name && if newTemplate.Name == template.Name &&
@@ -277,6 +310,8 @@ func (a *TransactionTemplatesApi) TemplateModifyHandler(c *core.WebContext) (any
} else if template.TemplateType == models.TRANSACTION_TEMPLATE_TYPE_SCHEDULE { } else if template.TemplateType == models.TRANSACTION_TEMPLATE_TYPE_SCHEDULE {
if newTemplate.ScheduledFrequencyType == template.ScheduledFrequencyType && if newTemplate.ScheduledFrequencyType == template.ScheduledFrequencyType &&
newTemplate.ScheduledFrequency == template.ScheduledFrequency && newTemplate.ScheduledFrequency == template.ScheduledFrequency &&
newTemplate.ScheduledStartTime == template.ScheduledStartTime &&
newTemplate.ScheduledEndTime == template.ScheduledEndTime &&
newTemplate.ScheduledAt == template.ScheduledAt && newTemplate.ScheduledAt == template.ScheduledAt &&
newTemplate.ScheduledTimezoneUtcOffset == template.ScheduledTimezoneUtcOffset { newTemplate.ScheduledTimezoneUtcOffset == template.ScheduledTimezoneUtcOffset {
return nil, errs.ErrNothingWillBeUpdated return nil, errs.ErrNothingWillBeUpdated
@@ -419,7 +454,7 @@ func (a *TransactionTemplatesApi) TemplateDeleteHandler(c *core.WebContext) (any
return true, nil return true, nil
} }
func (a *TransactionTemplatesApi) createNewTemplateModel(uid int64, templateCreateReq *models.TransactionTemplateCreateRequest, order int32) *models.TransactionTemplate { func (a *TransactionTemplatesApi) createNewTemplateModel(uid int64, templateCreateReq *models.TransactionTemplateCreateRequest, order int32) (*models.TransactionTemplate, error) {
template := &models.TransactionTemplate{ template := &models.TransactionTemplate{
Uid: uid, Uid: uid,
TemplateType: templateCreateReq.TemplateType, TemplateType: templateCreateReq.TemplateType,
@@ -441,9 +476,35 @@ func (a *TransactionTemplatesApi) createNewTemplateModel(uid int64, templateCrea
template.ScheduledFrequency = a.getOrderedFrequencyValues(*templateCreateReq.ScheduledFrequency) template.ScheduledFrequency = a.getOrderedFrequencyValues(*templateCreateReq.ScheduledFrequency)
template.ScheduledAt = a.getUTCScheduledAt(*templateCreateReq.ScheduledTimezoneUtcOffset) template.ScheduledAt = a.getUTCScheduledAt(*templateCreateReq.ScheduledTimezoneUtcOffset)
template.ScheduledTimezoneUtcOffset = *templateCreateReq.ScheduledTimezoneUtcOffset template.ScheduledTimezoneUtcOffset = *templateCreateReq.ScheduledTimezoneUtcOffset
if templateCreateReq.ScheduledStartDate != nil {
startTime, err := utils.ParseFromLongDateFirstTime(*templateCreateReq.ScheduledStartDate, *templateCreateReq.ScheduledTimezoneUtcOffset)
if err != nil {
return nil, err
}
startUnixTime := startTime.Unix()
template.ScheduledStartTime = &startUnixTime
}
if templateCreateReq.ScheduledEndDate != nil {
endTime, err := utils.ParseFromLongDateLastTime(*templateCreateReq.ScheduledEndDate, *templateCreateReq.ScheduledTimezoneUtcOffset)
if err != nil {
return nil, err
}
endUnixTime := endTime.Unix()
template.ScheduledEndTime = &endUnixTime
}
if template.ScheduledStartTime != nil && template.ScheduledEndTime != nil && *template.ScheduledStartTime > *template.ScheduledEndTime {
return nil, errs.ErrScheduledTransactionTemplateStartDataLaterThanEndDate
}
} }
return template return template, nil
} }
func (a *TransactionTemplatesApi) getUTCScheduledAt(scheduledTimezoneUtcOffset int16) int16 { func (a *TransactionTemplatesApi) getUTCScheduledAt(scheduledTimezoneUtcOffset int16) int16 {
+7 -6
View File
@@ -4,10 +4,11 @@ import "net/http"
// Error codes related to transaction templates // Error codes related to transaction templates
var ( var (
ErrTransactionTemplateIdInvalid = NewNormalError(NormalSubcategoryTemplate, 0, http.StatusBadRequest, "transaction template id is invalid") ErrTransactionTemplateIdInvalid = NewNormalError(NormalSubcategoryTemplate, 0, http.StatusBadRequest, "transaction template id is invalid")
ErrTransactionTemplateNotFound = NewNormalError(NormalSubcategoryTemplate, 1, http.StatusBadRequest, "transaction template not found") ErrTransactionTemplateNotFound = NewNormalError(NormalSubcategoryTemplate, 1, http.StatusBadRequest, "transaction template not found")
ErrTransactionTemplateTypeInvalid = NewNormalError(NormalSubcategoryTemplate, 2, http.StatusBadRequest, "transaction template type is invalid") ErrTransactionTemplateTypeInvalid = NewNormalError(NormalSubcategoryTemplate, 2, http.StatusBadRequest, "transaction template type is invalid")
ErrScheduledTransactionNotEnabled = NewNormalError(NormalSubcategoryTemplate, 3, http.StatusBadRequest, "scheduled transaction is not enabled") ErrScheduledTransactionNotEnabled = NewNormalError(NormalSubcategoryTemplate, 3, http.StatusBadRequest, "scheduled transaction is not enabled")
ErrScheduledTransactionFrequencyInvalid = NewNormalError(NormalSubcategoryTemplate, 4, http.StatusBadRequest, "scheduled transaction frequency is invalid") ErrScheduledTransactionFrequencyInvalid = NewNormalError(NormalSubcategoryTemplate, 4, http.StatusBadRequest, "scheduled transaction frequency is invalid")
ErrTransactionTemplateHasTooManyTags = NewNormalError(NormalSubcategoryTemplate, 5, http.StatusBadRequest, "transaction template has too many tags") ErrTransactionTemplateHasTooManyTags = NewNormalError(NormalSubcategoryTemplate, 5, http.StatusBadRequest, "transaction template has too many tags")
ErrScheduledTransactionTemplateStartDataLaterThanEndDate = NewNormalError(NormalSubcategoryTemplate, 6, http.StatusBadRequest, "scheduled transaction start date is later than end time")
) )
+25 -4
View File
@@ -2,6 +2,7 @@ package models
import ( import (
"strings" "strings"
"time"
"github.com/mayswind/ezbookkeeping/pkg/utils" "github.com/mayswind/ezbookkeeping/pkg/utils"
) )
@@ -29,15 +30,17 @@ const (
type TransactionTemplate struct { type TransactionTemplate struct {
TemplateId int64 `xorm:"PK"` TemplateId int64 `xorm:"PK"`
Uid int64 `xorm:"INDEX(IDX_transaction_template_uid_deleted_template_type_order) NOT NULL"` Uid int64 `xorm:"INDEX(IDX_transaction_template_uid_deleted_template_type_order) NOT NULL"`
Deleted bool `xorm:"INDEX(IDX_transaction_template_uid_deleted_template_type_order) INDEX(IDX_transaction_template_deleted_type_freqtype_scheduled_at) NOT NULL"` Deleted bool `xorm:"INDEX(IDX_transaction_template_uid_deleted_template_type_order) INDEX(IDX_transaction_template_deleted_type_freqtype_scheduled_time) NOT NULL"`
TemplateType TransactionTemplateType `xorm:"INDEX(IDX_transaction_template_uid_deleted_template_type_order) INDEX(IDX_transaction_template_deleted_type_freqtype_scheduled_at) NOT NULL"` TemplateType TransactionTemplateType `xorm:"INDEX(IDX_transaction_template_uid_deleted_template_type_order) INDEX(IDX_transaction_template_deleted_type_freqtype_scheduled_time) NOT NULL"`
Name string `xorm:"VARCHAR(64) NOT NULL"` Name string `xorm:"VARCHAR(64) NOT NULL"`
Type TransactionType `xorm:"NOT NULL"` Type TransactionType `xorm:"NOT NULL"`
CategoryId int64 `xorm:"NOT NULL"` CategoryId int64 `xorm:"NOT NULL"`
AccountId int64 `xorm:"NOT NULL"` AccountId int64 `xorm:"NOT NULL"`
ScheduledFrequencyType TransactionScheduleFrequencyType `xorm:"INDEX(IDX_transaction_template_deleted_type_freqtype_scheduled_at)"` ScheduledFrequencyType TransactionScheduleFrequencyType `xorm:"INDEX(IDX_transaction_template_deleted_type_freqtype_scheduled_time)"`
ScheduledFrequency string `xorm:"VARCHAR(100)"` ScheduledFrequency string `xorm:"VARCHAR(100)"`
ScheduledAt int16 `xorm:"INDEX(IDX_transaction_template_deleted_type_freqtype_scheduled_at)"` ScheduledStartTime *int64 `xorm:"INDEX(IDX_transaction_template_deleted_type_freqtype_scheduled_time)"`
ScheduledEndTime *int64 `xorm:"INDEX(IDX_transaction_template_deleted_type_freqtype_scheduled_time)"`
ScheduledAt int16 `xorm:"INDEX(IDX_transaction_template_deleted_type_freqtype_scheduled_time)"`
ScheduledTimezoneUtcOffset int16 ScheduledTimezoneUtcOffset int16
TagIds string `xorm:"VARCHAR(255) NOT NULL"` TagIds string `xorm:"VARCHAR(255) NOT NULL"`
Amount int64 `xorm:"NOT NULL"` Amount int64 `xorm:"NOT NULL"`
@@ -77,6 +80,8 @@ type TransactionTemplateCreateRequest struct {
Comment string `json:"comment" binding:"max=255"` Comment string `json:"comment" binding:"max=255"`
ScheduledFrequencyType *TransactionScheduleFrequencyType `json:"scheduledFrequencyType" binding:"omitempty"` ScheduledFrequencyType *TransactionScheduleFrequencyType `json:"scheduledFrequencyType" binding:"omitempty"`
ScheduledFrequency *string `json:"scheduledFrequency" binding:"omitempty"` ScheduledFrequency *string `json:"scheduledFrequency" binding:"omitempty"`
ScheduledStartDate *string `json:"scheduledStartDate" binding:"omitempty"`
ScheduledEndDate *string `json:"scheduledEndDate" binding:"omitempty"`
ScheduledTimezoneUtcOffset *int16 `json:"utcOffset" binding:"omitempty,min=-720,max=840"` ScheduledTimezoneUtcOffset *int16 `json:"utcOffset" binding:"omitempty,min=-720,max=840"`
ClientSessionId string `json:"clientSessionId"` ClientSessionId string `json:"clientSessionId"`
} }
@@ -102,6 +107,8 @@ type TransactionTemplateModifyRequest struct {
Comment string `json:"comment" binding:"max=255"` Comment string `json:"comment" binding:"max=255"`
ScheduledFrequencyType *TransactionScheduleFrequencyType `json:"scheduledFrequencyType" binding:"omitempty"` ScheduledFrequencyType *TransactionScheduleFrequencyType `json:"scheduledFrequencyType" binding:"omitempty"`
ScheduledFrequency *string `json:"scheduledFrequency" binding:"omitempty"` ScheduledFrequency *string `json:"scheduledFrequency" binding:"omitempty"`
ScheduledStartDate *string `json:"scheduledStartDate" binding:"omitempty"`
ScheduledEndDate *string `json:"scheduledEndDate" binding:"omitempty"`
ScheduledTimezoneUtcOffset *int16 `json:"utcOffset" binding:"omitempty,min=-720,max=840"` ScheduledTimezoneUtcOffset *int16 `json:"utcOffset" binding:"omitempty,min=-720,max=840"`
} }
@@ -133,6 +140,8 @@ type TransactionTemplateInfoResponse struct {
Name string `json:"name"` Name string `json:"name"`
ScheduledFrequencyType *TransactionScheduleFrequencyType `json:"scheduledFrequencyType,omitempty"` ScheduledFrequencyType *TransactionScheduleFrequencyType `json:"scheduledFrequencyType,omitempty"`
ScheduledFrequency *string `json:"scheduledFrequency,omitempty"` ScheduledFrequency *string `json:"scheduledFrequency,omitempty"`
ScheduledStartDate *string `json:"scheduledStartDate" binding:"omitempty"`
ScheduledEndDate *string `json:"scheduledEndDate" binding:"omitempty"`
ScheduledAt *int16 `json:"scheduledAt,omitempty"` ScheduledAt *int16 `json:"scheduledAt,omitempty"`
DisplayOrder int32 `json:"displayOrder"` DisplayOrder int32 `json:"displayOrder"`
Hidden bool `json:"hidden"` Hidden bool `json:"hidden"`
@@ -171,6 +180,18 @@ func (t *TransactionTemplate) ToTransactionTemplateInfoResponse(serverUtcOffset
response.ScheduledFrequencyType = &t.ScheduledFrequencyType response.ScheduledFrequencyType = &t.ScheduledFrequencyType
response.ScheduledFrequency = &t.ScheduledFrequency response.ScheduledFrequency = &t.ScheduledFrequency
response.ScheduledAt = &t.ScheduledAt response.ScheduledAt = &t.ScheduledAt
templateTimeZone := time.FixedZone("Template Timezone", int(t.ScheduledTimezoneUtcOffset)*60)
if t.ScheduledStartTime != nil {
startDate := utils.FormatUnixTimeToLongDate(*t.ScheduledStartTime, templateTimeZone)
response.ScheduledStartDate = &startDate
}
if t.ScheduledEndTime != nil {
endDate := utils.FormatUnixTimeToLongDate(*t.ScheduledEndTime, templateTimeZone)
response.ScheduledEndDate = &endDate
}
} }
return response return response
+1 -1
View File
@@ -136,7 +136,7 @@ func (s *TransactionTemplateService) ModifyTemplate(c core.Context, template *mo
template.UpdatedUnixTime = time.Now().Unix() template.UpdatedUnixTime = time.Now().Unix()
return s.UserDataDB(template.Uid).DoTransaction(c, func(sess *xorm.Session) error { return s.UserDataDB(template.Uid).DoTransaction(c, func(sess *xorm.Session) error {
updatedRows, err := sess.ID(template.TemplateId).Cols("name", "type", "category_id", "account_id", "scheduled_frequency_type", "scheduled_frequency", "scheduled_at", "scheduled_timezone_utc_offset", "tag_ids", "amount", "related_account_id", "related_account_amount", "hide_amount", "comment", "updated_unix_time").Where("uid=? AND deleted=?", template.Uid, false).Update(template) updatedRows, err := sess.ID(template.TemplateId).Cols("name", "type", "category_id", "account_id", "scheduled_frequency_type", "scheduled_frequency", "scheduled_start_time", "scheduled_end_time", "scheduled_at", "scheduled_timezone_utc_offset", "tag_ids", "amount", "related_account_id", "related_account_amount", "hide_amount", "comment", "updated_unix_time").Where("uid=? AND deleted=?", template.Uid, false).Update(template)
if err != nil { if err != nil {
return err return err
+13 -1
View File
@@ -398,7 +398,7 @@ func (s *TransactionService) CreateScheduledTransactions(c core.Context, current
for i := 0; i < s.UserDataDBCount(); i++ { for i := 0; i < s.UserDataDBCount(); i++ {
var templates []*models.TransactionTemplate var templates []*models.TransactionTemplate
err := s.UserDataDBByIndex(i).NewSession(c).Where("deleted=? AND template_type=? AND (scheduled_frequency_type=? OR scheduled_frequency_type=?) AND scheduled_at>=? AND scheduled_at<?", false, models.TRANSACTION_TEMPLATE_TYPE_SCHEDULE, models.TRANSACTION_SCHEDULE_FREQUENCY_TYPE_WEEKLY, models.TRANSACTION_SCHEDULE_FREQUENCY_TYPE_MONTHLY, minScheduledAt, maxScheduledAt).Find(&templates) err := s.UserDataDBByIndex(i).NewSession(c).Where("deleted=? AND template_type=? AND (scheduled_frequency_type=? OR scheduled_frequency_type=?) AND (scheduled_start_time IS NULL OR scheduled_start_time<=?) AND (scheduled_end_time IS NULL OR scheduled_end_time>=?) AND scheduled_at>=? AND scheduled_at<?", false, models.TRANSACTION_TEMPLATE_TYPE_SCHEDULE, models.TRANSACTION_SCHEDULE_FREQUENCY_TYPE_WEEKLY, models.TRANSACTION_SCHEDULE_FREQUENCY_TYPE_MONTHLY, startTime.Unix(), startTime.Unix(), minScheduledAt, maxScheduledAt).Find(&templates)
if err != nil { if err != nil {
return err return err
@@ -457,6 +457,18 @@ func (s *TransactionService) CreateScheduledTransactions(c core.Context, current
continue continue
} }
if template.ScheduledStartTime != nil && *template.ScheduledStartTime > transactionUnixTime {
skipCount++
log.Infof(c, "[transactions.CreateScheduledTransactions] transaction template \"id:%d\" does not need to create transaction, now is earlier than the start time %d", template.TemplateId, *template.ScheduledStartTime)
continue
}
if template.ScheduledEndTime != nil && *template.ScheduledEndTime < transactionUnixTime {
skipCount++
log.Infof(c, "[transactions.CreateScheduledTransactions] transaction template \"id:%d\" does not need to create transaction, now is later than the end time %d", template.TemplateId, *template.ScheduledEndTime)
continue
}
var transactionDbType models.TransactionDbType var transactionDbType models.TransactionDbType
if template.Type == models.TRANSACTION_TYPE_EXPENSE { if template.Type == models.TRANSACTION_TYPE_EXPENSE {
+30
View File
@@ -9,6 +9,7 @@ import (
) )
const ( const (
longDateFormat = "2006-01-02"
longDateTimeFormat = "2006-01-02 15:04:05" longDateTimeFormat = "2006-01-02 15:04:05"
longDateTimeWithTimezoneFormat = "2006-01-02 15:04:05Z07:00" longDateTimeWithTimezoneFormat = "2006-01-02 15:04:05Z07:00"
longDateTimeWithTimezoneFormat2 = "2006-01-02 15:04:05 Z0700" longDateTimeWithTimezoneFormat2 = "2006-01-02 15:04:05 Z0700"
@@ -42,6 +43,17 @@ func ParseNumericYearMonth(yearMonth string) (int32, int32, error) {
return year, month, nil return year, month, nil
} }
// FormatUnixTimeToLongDate returns a textual representation of the unix time formatted by long date time format
func FormatUnixTimeToLongDate(unixTime int64, timezone *time.Location) string {
t := parseFromUnixTime(unixTime)
if timezone != nil {
t = t.In(timezone)
}
return t.Format(longDateFormat)
}
// FormatUnixTimeToLongDateTime returns a textual representation of the unix time formatted by long date time format // FormatUnixTimeToLongDateTime returns a textual representation of the unix time formatted by long date time format
func FormatUnixTimeToLongDateTime(unixTime int64, timezone *time.Location) string { func FormatUnixTimeToLongDateTime(unixTime int64, timezone *time.Location) string {
t := parseFromUnixTime(unixTime) t := parseFromUnixTime(unixTime)
@@ -119,6 +131,24 @@ func GetMaxUnixTimeWithSameLocalDateTime(unixTime int64, currentUtcOffset int16)
return unixTime + int64(currentUtcOffset)*60 - westernmostTimezoneUtcOffset*60 return unixTime + int64(currentUtcOffset)*60 - westernmostTimezoneUtcOffset*60
} }
// ParseFromLongDateFirstTime parses a formatted string in long date format
func ParseFromLongDateFirstTime(t string, utcOffset int16) (time.Time, error) {
timezone := time.FixedZone("Timezone", int(utcOffset)*60)
return time.ParseInLocation(longDateFormat, t, timezone)
}
// ParseFromLongDateLastTime parses a formatted string in long date format
func ParseFromLongDateLastTime(t string, utcOffset int16) (time.Time, error) {
timezone := time.FixedZone("Timezone", int(utcOffset)*60)
lastTime, err := time.ParseInLocation(longDateFormat, t, timezone)
if err != nil {
return lastTime, err
}
return lastTime.Add(24 * time.Hour).Add(-1 * time.Nanosecond), nil
}
// ParseFromLongDateTimeToMinUnixTime parses a formatted string in long date time format to minimal unix time (the westernmost timezone) // ParseFromLongDateTimeToMinUnixTime parses a formatted string in long date time format to minimal unix time (the westernmost timezone)
func ParseFromLongDateTimeToMinUnixTime(t string) (time.Time, error) { func ParseFromLongDateTimeToMinUnixTime(t string) (time.Time, error) {
timezone := time.FixedZone("Timezone", easternmostTimezoneUtcOffset*60) timezone := time.FixedZone("Timezone", easternmostTimezoneUtcOffset*60)
+32
View File
@@ -18,6 +18,20 @@ func TestParseNumericYearMonth(t *testing.T) {
assert.Equal(t, expectedMonth, actualMonth) assert.Equal(t, expectedMonth, actualMonth)
} }
func TestFormatUnixTimeToLongDate(t *testing.T) {
unixTime := int64(1617228083)
utcTimezone := time.FixedZone("Test Timezone", 0) // UTC
utc8Timezone := time.FixedZone("Test Timezone", 28800) // UTC+8
expectedValue := "2021-03-31"
actualValue := FormatUnixTimeToLongDate(unixTime, utcTimezone)
assert.Equal(t, expectedValue, actualValue)
expectedValue = "2021-04-01"
actualValue = FormatUnixTimeToLongDate(unixTime, utc8Timezone)
assert.Equal(t, expectedValue, actualValue)
}
func TestFormatUnixTimeToLongDateTime(t *testing.T) { func TestFormatUnixTimeToLongDateTime(t *testing.T) {
unixTime := int64(1617228083) unixTime := int64(1617228083)
utcTimezone := time.FixedZone("Test Timezone", 0) // UTC utcTimezone := time.FixedZone("Test Timezone", 0) // UTC
@@ -106,6 +120,24 @@ func TestGetMaxUnixTimeWithSameLocalDateTime(t *testing.T) {
assert.Equal(t, expectedValue, actualValue) assert.Equal(t, expectedValue, actualValue)
} }
func TestParseFromLongDateFirstTime(t *testing.T) {
expectedValue := int64(1690819200)
actualTime, err := ParseFromLongDateFirstTime("2023-08-01", 480)
assert.Equal(t, nil, err)
actualValue := actualTime.Unix()
assert.Equal(t, expectedValue, actualValue)
}
func TestParseFromLongDateLastTime(t *testing.T) {
expectedValue := int64(1690905599)
actualTime, err := ParseFromLongDateLastTime("2023-08-01", 480)
assert.Equal(t, nil, err)
actualValue := actualTime.Unix()
assert.Equal(t, expectedValue, actualValue)
}
func TestParseFromLongDateTimeToMinUnixTime(t *testing.T) { func TestParseFromLongDateTimeToMinUnixTime(t *testing.T) {
expectedValue := int64(1690797600) expectedValue := int64(1690797600)
actualTime, err := ParseFromLongDateTimeToMinUnixTime("2023-08-01 00:00:00") actualTime, err := ParseFromLongDateTimeToMinUnixTime("2023-08-01 00:00:00")
+99
View File
@@ -0,0 +1,99 @@
<template>
<v-select
persistent-placeholder
:readonly="readonly"
:disabled="disabled"
:clearable="modelValue ? clearable : false"
:label="label"
:menu-props="{ 'content-class': 'date-select-menu' }"
v-model="dateTime"
>
<template #selection>
<span class="text-truncate cursor-pointer">{{ displayTime }}</span>
</template>
<template #no-data>
<vue-date-picker inline vertical auto-apply
ref="datepicker"
month-name-format="long"
model-type="yyyy-MM-dd"
:clearable="true"
:enable-time-picker="false"
:dark="isDarkMode"
:week-start="firstDayOfWeek"
:year-range="yearRange"
:day-names="dayNames"
:year-first="isYearFirst"
v-model="dateTime">
<template #month="{ text }">
{{ getMonthShortName(text) }}
</template>
<template #month-overlay-value="{ text }">
{{ getMonthShortName(text) }}
</template>
</vue-date-picker>
</template>
</v-select>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useTheme } from 'vuetify';
import { useI18n } from '@/locales/helpers.ts';
import { useUserStore } from '@/stores/user.ts';
import { ThemeType } from '@/core/theme.ts';
import { arrangeArrayWithNewStartIndex } from '@/lib/common.ts';
import { getCurrentYear } from '@/lib/datetime.ts';
const props = defineProps<{
modelValue?: string;
disabled?: boolean;
readonly?: boolean;
clearable?: boolean;
label?: string;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
}>();
const theme = useTheme();
const { tt, getAllMinWeekdayNames, getMonthShortName, formatDateToLongDate, isLongDateMonthAfterYear } = useI18n();
const userStore = useUserStore();
const yearRange = ref<number[]>([
2000,
getCurrentYear() + 1
]);
const dateTime = computed<string>({
get: () => props.modelValue ?? '',
set: (value: string) => emit('update:modelValue', value)
});
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
const firstDayOfWeek = computed<number>(() => userStore.currentUserFirstDayOfWeek);
const dayNames = computed<string[]>(() => arrangeArrayWithNewStartIndex(getAllMinWeekdayNames(), firstDayOfWeek.value));
const isYearFirst = computed<boolean>(() => isLongDateMonthAfterYear());
const displayTime = computed<string>(() => {
if (props.modelValue) {
return formatDateToLongDate(props.modelValue);
} else {
return tt('Unspecified');
}
});
</script>
<style>
.date-select-menu {
max-height: inherit !important;
}
.date-select-menu .dp__menu {
border: 0;
}
</style>
@@ -0,0 +1,100 @@
<template>
<f7-sheet swipe-to-close swipe-handler=".swipe-handler" class="date-selection-sheet" style="height:auto"
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
<f7-toolbar>
<div class="swipe-handler"></div>
<div class="left">
<f7-link :text="tt('Clear')" @click="clear"></f7-link>
</div>
<div class="right">
<f7-link :text="tt('Done')" @click="confirm"></f7-link>
</div>
</f7-toolbar>
<f7-page-content>
<div class="block block-outline no-margin no-padding">
<vue-date-picker inline auto-apply
month-name-format="long"
model-type="yyyy-MM-dd"
six-weeks="center"
class="justify-content-center"
:enable-time-picker="false"
:clearable="true"
:dark="isDarkMode"
:week-start="firstDayOfWeek"
:year-range="yearRange"
:day-names="dayNames"
:year-first="isYearFirst"
v-model="dateTime">
<template #month="{ text }">
{{ getMonthShortName(text) }}
</template>
<template #month-overlay-value="{ text }">
{{ getMonthShortName(text) }}
</template>
</vue-date-picker>
</div>
</f7-page-content>
</f7-sheet>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useEnvironmentsStore } from '@/stores/environment.ts';
import { useUserStore } from '@/stores/user.ts';
import { arrangeArrayWithNewStartIndex } from '@/lib/common.ts';
import { getCurrentYear } from '@/lib/datetime.ts';
const props = defineProps<{
modelValue?: string;
show: boolean;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
(e: 'update:show', value: boolean): void;
}>();
const { tt, getAllMinWeekdayNames, getMonthShortName, isLongDateMonthAfterYear } = useI18n();
const environmentsStore = useEnvironmentsStore();
const userStore = useUserStore();
const yearRange = ref<number[]>([
2000,
getCurrentYear() + 1
]);
const dateTime = ref<string>('');
const isDarkMode = computed<boolean>(() => environmentsStore.framework7DarkMode || false);
const firstDayOfWeek = computed<number>(() => userStore.currentUserFirstDayOfWeek);
const dayNames = computed<string[]>(() => arrangeArrayWithNewStartIndex(getAllMinWeekdayNames(), firstDayOfWeek.value));
const isYearFirst = computed<boolean>(() => isLongDateMonthAfterYear());
function clear(): void {
dateTime.value = '';
confirm();
}
function confirm(): void {
emit('update:modelValue', dateTime.value);
emit('update:show', false);
}
function onSheetOpen(): void {
dateTime.value = props.modelValue ?? '';
}
function onSheetClosed(): void {
emit('update:show', false);
}
</script>
<style>
.date-selection-sheet .dp__menu {
border: 0;
}
</style>
+2
View File
@@ -79,6 +79,7 @@ import ItemIcon from '@/components/desktop/ItemIcon.vue';
import BtnVerticalGroup from '@/components/desktop/BtnVerticalGroup.vue'; import BtnVerticalGroup from '@/components/desktop/BtnVerticalGroup.vue';
import AmountInput from '@/components/desktop/AmountInput.vue'; import AmountInput from '@/components/desktop/AmountInput.vue';
import DateTimeSelect from '@/components/desktop/DateTimeSelect.vue'; import DateTimeSelect from '@/components/desktop/DateTimeSelect.vue';
import DateSelect from '@/components/desktop/DateSelect.vue';
import ColorSelect from '@/components/desktop/ColorSelect.vue'; import ColorSelect from '@/components/desktop/ColorSelect.vue';
import IconSelect from '@/components/desktop/IconSelect.vue'; import IconSelect from '@/components/desktop/IconSelect.vue';
import TwoColumnSelect from '@/components/desktop/TwoColumnSelect.vue'; import TwoColumnSelect from '@/components/desktop/TwoColumnSelect.vue';
@@ -450,6 +451,7 @@ app.component('ItemIcon', ItemIcon);
app.component('BtnVerticalGroup', BtnVerticalGroup); app.component('BtnVerticalGroup', BtnVerticalGroup);
app.component('AmountInput', AmountInput); app.component('AmountInput', AmountInput);
app.component('DateTimeSelect', DateTimeSelect); app.component('DateTimeSelect', DateTimeSelect);
app.component('DateSelect', DateSelect);
app.component('ColorSelect', ColorSelect); app.component('ColorSelect', ColorSelect);
app.component('IconSelect', IconSelect); app.component('IconSelect', IconSelect);
app.component('TwoColumnSelect', TwoColumnSelect); app.component('TwoColumnSelect', TwoColumnSelect);
+4
View File
@@ -199,6 +199,10 @@ export function formatCurrentTime(format: string): string {
return moment().format(format); return moment().format(format);
} }
export function formatDate(date: string, format: string): string {
return moment(date, 'YYYY-MM-DD').format(format);
}
export function getUnixTime(date: SupportedDate): number { export function getUnixTime(date: SupportedDate): number {
return moment(date).unix(); return moment(date).unix();
} }
+3
View File
@@ -1123,6 +1123,7 @@
"scheduled transaction is not enabled": "Geplante Transaktion ist nicht aktiviert", "scheduled transaction is not enabled": "Geplante Transaktion ist nicht aktiviert",
"scheduled transaction frequency is invalid": "Häufigkeit der geplanten Transaktion ist ungültig", "scheduled transaction frequency is invalid": "Häufigkeit der geplanten Transaktion ist ungültig",
"transaction template has too many tags": "Transaktionsvorlage hat zu viele Tags", "transaction template has too many tags": "Transaktionsvorlage hat zu viele Tags",
"scheduled transaction start date is later than end time": "Scheduled transaction start date is later than end time",
"transaction picture id is invalid": "Transaktionsbild-ID ist ungültig", "transaction picture id is invalid": "Transaktionsbild-ID ist ungültig",
"transaction picture not found": "Transaktionsbild nicht gefunden", "transaction picture not found": "Transaktionsbild nicht gefunden",
"no transaction picture": "Kein Transaktionsbild vorhanden", "no transaction picture": "Kein Transaktionsbild vorhanden",
@@ -1289,6 +1290,8 @@
"Previous Billing Cycle": "Vorheriger Abrechnungszeitraum", "Previous Billing Cycle": "Vorheriger Abrechnungszeitraum",
"Current Billing Cycle": "Aktueller Abrechnungszeitraum", "Current Billing Cycle": "Aktueller Abrechnungszeitraum",
"Custom Date": "Benutzerdefiniertes Datum", "Custom Date": "Benutzerdefiniertes Datum",
"Start Date": "Start Date",
"End Date": "End Date",
"Start Time": "Startzeit", "Start Time": "Startzeit",
"End Time": "Endzeit", "End Time": "Endzeit",
"Select Date": "Datum auswählen", "Select Date": "Datum auswählen",
+3
View File
@@ -1123,6 +1123,7 @@
"scheduled transaction is not enabled": "Scheduled transaction is not enabled", "scheduled transaction is not enabled": "Scheduled transaction is not enabled",
"scheduled transaction frequency is invalid": "Scheduled transaction frequency is invalid", "scheduled transaction frequency is invalid": "Scheduled transaction frequency is invalid",
"transaction template has too many tags": "There are too many tags in this transaction template", "transaction template has too many tags": "There are too many tags in this transaction template",
"scheduled transaction start date is later than end time": "Scheduled transaction start date is later than end time",
"transaction picture id is invalid": "Transaction picture ID is invalid", "transaction picture id is invalid": "Transaction picture ID is invalid",
"transaction picture not found": "Transaction picture is not found", "transaction picture not found": "Transaction picture is not found",
"no transaction picture": "There is no transaction picture file", "no transaction picture": "There is no transaction picture file",
@@ -1289,6 +1290,8 @@
"Previous Billing Cycle": "Previous Billing Cycle", "Previous Billing Cycle": "Previous Billing Cycle",
"Current Billing Cycle": "Current Billing Cycle", "Current Billing Cycle": "Current Billing Cycle",
"Custom Date": "Custom Date", "Custom Date": "Custom Date",
"Start Date": "Start Date",
"End Date": "End Date",
"Start Time": "Start Time", "Start Time": "Start Time",
"End Time": "End Time", "End Time": "End Time",
"Select Date": "Select Date", "Select Date": "Select Date",
+3
View File
@@ -1123,6 +1123,7 @@
"scheduled transaction is not enabled": "La transacción programada no está habilitada", "scheduled transaction is not enabled": "La transacción programada no está habilitada",
"scheduled transaction frequency is invalid": "La frecuencia de transacción programada no es válida", "scheduled transaction frequency is invalid": "La frecuencia de transacción programada no es válida",
"transaction template has too many tags": "Hay demasiadas etiquetas en esta plantilla de transacción", "transaction template has too many tags": "Hay demasiadas etiquetas en esta plantilla de transacción",
"scheduled transaction start date is later than end time": "Scheduled transaction start date is later than end time",
"transaction picture id is invalid": "El ID de la imagen de la transacción no es válido", "transaction picture id is invalid": "El ID de la imagen de la transacción no es válido",
"transaction picture not found": "No se encuentra la imagen de la transacción", "transaction picture not found": "No se encuentra la imagen de la transacción",
"no transaction picture": "No hay ningún archivo de imagen de transacción.", "no transaction picture": "No hay ningún archivo de imagen de transacción.",
@@ -1289,6 +1290,8 @@
"Previous Billing Cycle": "Ciclo de facturación anterior", "Previous Billing Cycle": "Ciclo de facturación anterior",
"Current Billing Cycle": "Ciclo de facturación actual", "Current Billing Cycle": "Ciclo de facturación actual",
"Custom Date": "Fecha personalizada", "Custom Date": "Fecha personalizada",
"Start Date": "Start Date",
"End Date": "End Date",
"Start Time": "Hora de inicio", "Start Time": "Hora de inicio",
"End Time": "Hora de finalización", "End Time": "Hora de finalización",
"Select Date": "Seleccionar fecha", "Select Date": "Seleccionar fecha",
+6
View File
@@ -117,6 +117,7 @@ import {
isPM, isPM,
formatUnixTime, formatUnixTime,
formatCurrentTime, formatCurrentTime,
formatDate,
parseDateFromUnixTime, parseDateFromUnixTime,
getYear, getYear,
getTimezoneOffset, getTimezoneOffset,
@@ -1297,6 +1298,10 @@ export function useI18n() {
return getLocalizedDateTimeType(ShortTimeFormat.all(), ShortTimeFormat.values(), userStore.currentUserShortTimeFormat, 'shortTimeFormat', ShortTimeFormat.Default).isMeridiemIndicatorFirst || false; return getLocalizedDateTimeType(ShortTimeFormat.all(), ShortTimeFormat.values(), userStore.currentUserShortTimeFormat, 'shortTimeFormat', ShortTimeFormat.Default).isMeridiemIndicatorFirst || false;
} }
function formatDateToLongDate(date: string): string {
return formatDate(date, getLocalizedLongDateFormat());
}
function formatYearQuarter(year: number, quarter: number): string { function formatYearQuarter(year: number, quarter: number): string {
if (1 <= quarter && quarter <= 4) { if (1 <= quarter && quarter <= 4) {
return t('format.yearQuarter.q' + quarter, { return t('format.yearQuarter.q' + quarter, {
@@ -1713,6 +1718,7 @@ export function useI18n() {
formatUnixTimeToShortMonthDay: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortMonthDayFormat(), utcOffset, currentUtcOffset), formatUnixTimeToShortMonthDay: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortMonthDayFormat(), utcOffset, currentUtcOffset),
formatUnixTimeToLongTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongTimeFormat(), utcOffset, currentUtcOffset), formatUnixTimeToLongTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedLongTimeFormat(), utcOffset, currentUtcOffset),
formatUnixTimeToShortTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortTimeFormat(), utcOffset, currentUtcOffset), formatUnixTimeToShortTime: (unixTime: number, utcOffset?: number, currentUtcOffset?: number) => formatUnixTime(unixTime, getLocalizedShortTimeFormat(), utcOffset, currentUtcOffset),
formatDateToLongDate,
formatYearQuarter, formatYearQuarter,
formatDateRange, formatDateRange,
getTimezoneDifferenceDisplayText, getTimezoneDifferenceDisplayText,
+3
View File
@@ -1123,6 +1123,7 @@
"scheduled transaction is not enabled": "Запланированная транзакция не включена", "scheduled transaction is not enabled": "Запланированная транзакция не включена",
"scheduled transaction frequency is invalid": "Частота запланированной транзакции недействительна", "scheduled transaction frequency is invalid": "Частота запланированной транзакции недействительна",
"transaction template has too many tags": "Слишком много тегов в этом шаблоне транзакции", "transaction template has too many tags": "Слишком много тегов в этом шаблоне транзакции",
"scheduled transaction start date is later than end time": "Scheduled transaction start date is later than end time",
"transaction picture id is invalid": "ID изображения транзакции недействителен", "transaction picture id is invalid": "ID изображения транзакции недействителен",
"transaction picture not found": "Изображение транзакции не найдено", "transaction picture not found": "Изображение транзакции не найдено",
"no transaction picture": "Нет файла изображения транзакции", "no transaction picture": "Нет файла изображения транзакции",
@@ -1289,6 +1290,8 @@
"Previous Billing Cycle": "Предыдущий расчетный период", "Previous Billing Cycle": "Предыдущий расчетный период",
"Current Billing Cycle": "Текущий расчетный период", "Current Billing Cycle": "Текущий расчетный период",
"Custom Date": "Выбрать дату", "Custom Date": "Выбрать дату",
"Start Date": "Start Date",
"End Date": "End Date",
"Start Time": "Время начала", "Start Time": "Время начала",
"End Time": "Время окончания", "End Time": "Время окончания",
"Select Date": "Выбрать дату", "Select Date": "Выбрать дату",
+3
View File
@@ -1123,6 +1123,7 @@
"scheduled transaction is not enabled": "Giao dịch theo lịch trình chưa được bật", "scheduled transaction is not enabled": "Giao dịch theo lịch trình chưa được bật",
"scheduled transaction frequency is invalid": "Tần suất giao dịch theo lịch trình không hợp lệ", "scheduled transaction frequency is invalid": "Tần suất giao dịch theo lịch trình không hợp lệ",
"transaction template has too many tags": "Có quá nhiều thẻ trong mẫu giao dịch này", "transaction template has too many tags": "Có quá nhiều thẻ trong mẫu giao dịch này",
"scheduled transaction start date is later than end time": "Scheduled transaction start date is later than end time",
"transaction picture id is invalid": "ID ảnh giao dịch không hợp lệ", "transaction picture id is invalid": "ID ảnh giao dịch không hợp lệ",
"transaction picture not found": "Không tìm thấy ảnh giao dịch", "transaction picture not found": "Không tìm thấy ảnh giao dịch",
"no transaction picture": "Không có tệp ảnh giao dịch", "no transaction picture": "Không có tệp ảnh giao dịch",
@@ -1289,6 +1290,8 @@
"Previous Billing Cycle": "Previous Billing Cycle", "Previous Billing Cycle": "Previous Billing Cycle",
"Current Billing Cycle": "Current Billing Cycle", "Current Billing Cycle": "Current Billing Cycle",
"Custom Date": "Ngày tùy chỉnh", "Custom Date": "Ngày tùy chỉnh",
"Start Date": "Start Date",
"End Date": "End Date",
"Start Time": "Thời gian bắt đầu", "Start Time": "Thời gian bắt đầu",
"End Time": "Thời gian kết thúc", "End Time": "Thời gian kết thúc",
"Select Date": "Chọn ngày", "Select Date": "Chọn ngày",
+3
View File
@@ -1123,6 +1123,7 @@
"scheduled transaction is not enabled": "定时交易没有启用", "scheduled transaction is not enabled": "定时交易没有启用",
"scheduled transaction frequency is invalid": "定时交易周期无效", "scheduled transaction frequency is invalid": "定时交易周期无效",
"transaction template has too many tags": "交易模板中的标签过多", "transaction template has too many tags": "交易模板中的标签过多",
"scheduled transaction start date is later than end time": "定时交易开始时间晚于结束时间",
"transaction picture id is invalid": "交易图片ID无效", "transaction picture id is invalid": "交易图片ID无效",
"transaction picture not found": "交易图片不存在", "transaction picture not found": "交易图片不存在",
"no transaction picture": "没有交易图片文件", "no transaction picture": "没有交易图片文件",
@@ -1289,6 +1290,8 @@
"Previous Billing Cycle": "上个账单周期", "Previous Billing Cycle": "上个账单周期",
"Current Billing Cycle": "当前账单周期", "Current Billing Cycle": "当前账单周期",
"Custom Date": "自定义日期", "Custom Date": "自定义日期",
"Start Date": "开始日期",
"End Date": "结束日期",
"Start Time": "开始时间", "Start Time": "开始时间",
"End Time": "结束时间", "End Time": "结束时间",
"Select Date": "选择日期", "Select Date": "选择日期",
+2
View File
@@ -92,6 +92,7 @@ import PinCodeInputSheet from '@/components/mobile/PinCodeInputSheet.vue';
import PasswordInputSheet from '@/components/mobile/PasswordInputSheet.vue'; import PasswordInputSheet from '@/components/mobile/PasswordInputSheet.vue';
import PasscodeInputSheet from '@/components/mobile/PasscodeInputSheet.vue'; import PasscodeInputSheet from '@/components/mobile/PasscodeInputSheet.vue';
import DateTimeSelectionSheet from '@/components/mobile/DateTimeSelectionSheet.vue'; import DateTimeSelectionSheet from '@/components/mobile/DateTimeSelectionSheet.vue';
import DateSelectionSheet from '@/components/mobile/DateSelectionSheet.vue';
import DateRangeSelectionSheet from '@/components/mobile/DateRangeSelectionSheet.vue'; import DateRangeSelectionSheet from '@/components/mobile/DateRangeSelectionSheet.vue';
import MonthRangeSelectionSheet from '@/components/mobile/MonthRangeSelectionSheet.vue'; import MonthRangeSelectionSheet from '@/components/mobile/MonthRangeSelectionSheet.vue';
import ListItemSelectionSheet from '@/components/mobile/ListItemSelectionSheet.vue'; import ListItemSelectionSheet from '@/components/mobile/ListItemSelectionSheet.vue';
@@ -175,6 +176,7 @@ app.component('PinCodeInputSheet', PinCodeInputSheet);
app.component('PasswordInputSheet', PasswordInputSheet); app.component('PasswordInputSheet', PasswordInputSheet);
app.component('PasscodeInputSheet', PasscodeInputSheet); app.component('PasscodeInputSheet', PasscodeInputSheet);
app.component('DateTimeSelectionSheet', DateTimeSelectionSheet); app.component('DateTimeSelectionSheet', DateTimeSelectionSheet);
app.component('DateSelectionSheet', DateSelectionSheet);
app.component('DateRangeSelectionSheet', DateRangeSelectionSheet); app.component('DateRangeSelectionSheet', DateRangeSelectionSheet);
app.component('MonthRangeSelectionSheet', MonthRangeSelectionSheet); app.component('MonthRangeSelectionSheet', MonthRangeSelectionSheet);
app.component('ListItemSelectionSheet', ListItemSelectionSheet); app.component('ListItemSelectionSheet', ListItemSelectionSheet);
+21 -1
View File
@@ -8,16 +8,20 @@ export class TransactionTemplate extends Transaction implements TransactionTempl
public name: string; public name: string;
public scheduledFrequencyType?: number; public scheduledFrequencyType?: number;
public scheduledFrequency?: string; public scheduledFrequency?: string;
public scheduledStartDate?: string;
public scheduledEndDate?: string;
public scheduledAt?: number; public scheduledAt?: number;
public displayOrder: number; public displayOrder: number;
public hidden: boolean; public hidden: boolean;
private constructor(id: string, templateType: number, name: string, type: number, categoryId: string, utcOffset: number, sourceAccountId: string, destinationAccountId: string, sourceAmount: number, destinationAmount: number, hideAmount: boolean, scheduledFrequencyType: number | undefined, scheduledFrequency: string | undefined, scheduledAt: number | undefined, tagIds: string[], comment: string, editable: boolean, displayOrder: number, hidden: boolean) { private constructor(id: string, templateType: number, name: string, type: number, categoryId: string, utcOffset: number, sourceAccountId: string, destinationAccountId: string, sourceAmount: number, destinationAmount: number, hideAmount: boolean, scheduledFrequencyType: number | undefined, scheduledFrequency: string | undefined, scheduledStartDate: string | undefined, scheduledEndDate: string | undefined, scheduledAt: number | undefined, tagIds: string[], comment: string, editable: boolean, displayOrder: number, hidden: boolean) {
super(id, '', type, categoryId, 0, undefined, utcOffset, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment, editable); super(id, '', type, categoryId, 0, undefined, utcOffset, sourceAccountId, destinationAccountId, sourceAmount, destinationAmount, hideAmount, tagIds, comment, editable);
this.templateType = templateType; this.templateType = templateType;
this.name = name; this.name = name;
this.scheduledFrequencyType = scheduledFrequencyType; this.scheduledFrequencyType = scheduledFrequencyType;
this.scheduledFrequency = scheduledFrequency; this.scheduledFrequency = scheduledFrequency;
this.scheduledStartDate = scheduledStartDate;
this.scheduledEndDate = scheduledEndDate;
this.scheduledAt = scheduledAt; this.scheduledAt = scheduledAt;
this.displayOrder = displayOrder; this.displayOrder = displayOrder;
this.hidden = hidden; this.hidden = hidden;
@@ -30,6 +34,8 @@ export class TransactionTemplate extends Transaction implements TransactionTempl
if (this.templateType === TemplateType.Schedule.type) { if (this.templateType === TemplateType.Schedule.type) {
this.scheduledFrequencyType = other.scheduledFrequencyType; this.scheduledFrequencyType = other.scheduledFrequencyType;
this.scheduledFrequency = other.scheduledFrequency; this.scheduledFrequency = other.scheduledFrequency;
this.scheduledStartDate = other.scheduledStartDate;
this.scheduledEndDate = other.scheduledEndDate;
this.utcOffset = other.utcOffset; this.utcOffset = other.utcOffset;
this.timeZone = undefined; this.timeZone = undefined;
} }
@@ -50,6 +56,8 @@ export class TransactionTemplate extends Transaction implements TransactionTempl
comment: this.comment, comment: this.comment,
scheduledFrequencyType: this.templateType === TemplateType.Schedule.type ? this.scheduledFrequencyType : undefined, scheduledFrequencyType: this.templateType === TemplateType.Schedule.type ? this.scheduledFrequencyType : undefined,
scheduledFrequency: this.templateType === TemplateType.Schedule.type ? this.scheduledFrequency : undefined, scheduledFrequency: this.templateType === TemplateType.Schedule.type ? this.scheduledFrequency : undefined,
scheduledStartDate: this.templateType === TemplateType.Schedule.type && this.scheduledStartDate ? this.scheduledStartDate : undefined,
scheduledEndDate: this.templateType === TemplateType.Schedule.type && this.scheduledEndDate ? this.scheduledEndDate : undefined,
utcOffset: this.templateType === TemplateType.Schedule.type ? this.utcOffset : undefined, utcOffset: this.templateType === TemplateType.Schedule.type ? this.utcOffset : undefined,
clientSessionId: clientSessionId clientSessionId: clientSessionId
}; };
@@ -70,6 +78,8 @@ export class TransactionTemplate extends Transaction implements TransactionTempl
comment: this.comment, comment: this.comment,
scheduledFrequencyType: this.templateType === TemplateType.Schedule.type ? this.scheduledFrequencyType : undefined, scheduledFrequencyType: this.templateType === TemplateType.Schedule.type ? this.scheduledFrequencyType : undefined,
scheduledFrequency: this.templateType === TemplateType.Schedule.type ? this.scheduledFrequency : undefined, scheduledFrequency: this.templateType === TemplateType.Schedule.type ? this.scheduledFrequency : undefined,
scheduledStartDate: this.templateType === TemplateType.Schedule.type && this.scheduledStartDate ? this.scheduledStartDate : undefined,
scheduledEndDate: this.templateType === TemplateType.Schedule.type && this.scheduledEndDate ? this.scheduledEndDate : undefined,
utcOffset: this.templateType === TemplateType.Schedule.type ? this.utcOffset : undefined utcOffset: this.templateType === TemplateType.Schedule.type ? this.utcOffset : undefined
}; };
} }
@@ -89,6 +99,8 @@ export class TransactionTemplate extends Transaction implements TransactionTempl
transaction.hideAmount, transaction.hideAmount,
undefined, // scheduledFrequencyType undefined, // scheduledFrequencyType
undefined, // scheduledFrequency undefined, // scheduledFrequency
undefined, // scheduledStartDate
undefined, // scheduledEndDate
undefined, // scheduledAt undefined, // scheduledAt
transaction.tagIds, transaction.tagIds,
transaction.comment, transaction.comment,
@@ -113,6 +125,8 @@ export class TransactionTemplate extends Transaction implements TransactionTempl
templateResponse.hideAmount, templateResponse.hideAmount,
templateResponse.scheduledFrequencyType, templateResponse.scheduledFrequencyType,
templateResponse.scheduledFrequency, templateResponse.scheduledFrequency,
templateResponse.scheduledStartDate ?? undefined,
templateResponse.scheduledEndDate ?? undefined,
templateResponse.scheduledAt, templateResponse.scheduledAt,
templateResponse.tagIds, templateResponse.tagIds,
templateResponse.comment, templateResponse.comment,
@@ -147,6 +161,8 @@ export interface TransactionTemplateCreateRequest {
readonly comment: string; readonly comment: string;
readonly scheduledFrequencyType?: number; readonly scheduledFrequencyType?: number;
readonly scheduledFrequency?: string; readonly scheduledFrequency?: string;
readonly scheduledStartDate?: string;
readonly scheduledEndDate?: string;
readonly utcOffset?: number; readonly utcOffset?: number;
readonly clientSessionId: string; readonly clientSessionId: string;
} }
@@ -165,6 +181,8 @@ export interface TransactionTemplateModifyRequest {
readonly comment: string; readonly comment: string;
readonly scheduledFrequencyType?: number; readonly scheduledFrequencyType?: number;
readonly scheduledFrequency?: string; readonly scheduledFrequency?: string;
readonly scheduledStartDate?: string;
readonly scheduledEndDate?: string;
readonly utcOffset?: number; readonly utcOffset?: number;
} }
@@ -191,6 +209,8 @@ export interface TransactionTemplateInfoResponse extends TransactionInfoResponse
readonly name: string; readonly name: string;
readonly scheduledFrequencyType?: number; readonly scheduledFrequencyType?: number;
readonly scheduledFrequency?: string; readonly scheduledFrequency?: string;
readonly scheduledStartDate?: string;
readonly scheduledEndDate?: string;
readonly scheduledAt?: number; readonly scheduledAt?: number;
readonly displayOrder: number; readonly displayOrder: number;
readonly hidden: boolean; readonly hidden: boolean;
@@ -235,6 +235,22 @@
</template> </template>
</v-autocomplete> </v-autocomplete>
</v-col> </v-col>
<v-col cols="12" md="6" v-if="type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type">
<date-select
:readonly="mode === TransactionEditPageMode.View"
:disabled="loading || submitting"
:clearable="true"
:label="tt('Start Date')"
v-model="transaction.scheduledStartDate" />
</v-col>
<v-col cols="12" md="6" v-if="type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type">
<date-select
:readonly="mode === TransactionEditPageMode.View"
:disabled="loading || submitting"
:clearable="true"
:label="tt('End Date')"
v-model="transaction.scheduledEndDate" />
</v-col>
<v-col cols="12" md="12" v-if="type === TransactionEditPageType.Transaction"> <v-col cols="12" md="12" v-if="type === TransactionEditPageType.Transaction">
<v-select <v-select
persistent-placeholder persistent-placeholder
+60 -1
View File
@@ -271,6 +271,34 @@
</schedule-frequency-sheet> </schedule-frequency-sheet>
</f7-list-item> </f7-list-item>
<f7-list-item
class="transaction-edit-datetime list-item-with-header-and-title"
link="#" no-chevron
:class="{ 'readonly': mode === TransactionEditPageMode.View }"
:header="tt('Start Date')"
:title="transactionDisplayScheduledStartDate"
@click="showScheduledStartDateSheet = true"
v-if="pageTypeAndMode?.type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type"
>
<date-selection-sheet v-model:show="showScheduledStartDateSheet"
v-model="transaction.scheduledStartDate">
</date-selection-sheet>
</f7-list-item>
<f7-list-item
class="transaction-edit-datetime list-item-with-header-and-title"
link="#" no-chevron
:class="{ 'readonly': mode === TransactionEditPageMode.View }"
:header="tt('End Date')"
:title="transactionDisplayScheduledEndDate"
@click="showScheduledEndDateSheet = true"
v-if="pageTypeAndMode?.type === TransactionEditPageType.Template && transaction instanceof TransactionTemplate && transaction.templateType === TemplateType.Schedule.type"
>
<date-selection-sheet v-model:show="showScheduledEndDateSheet"
v-model="transaction.scheduledEndDate">
</date-selection-sheet>
</f7-list-item>
<f7-list-item <f7-list-item
:no-chevron="mode === TransactionEditPageMode.View" :no-chevron="mode === TransactionEditPageMode.View"
class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after" class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after"
@@ -497,7 +525,8 @@ const {
getMultiMonthdayShortNames, getMultiMonthdayShortNames,
getMultiWeekdayLongNames, getMultiWeekdayLongNames,
formatUnixTimeToLongDate, formatUnixTimeToLongDate,
formatUnixTimeToLongTime formatUnixTimeToLongTime,
formatDateToLongDate
} = useI18n(); } = useI18n();
const { showAlert, showConfirm, showToast, routeBackOnError } = useI18nUIComponents(); const { showAlert, showConfirm, showToast, routeBackOnError } = useI18nUIComponents();
@@ -574,6 +603,8 @@ const showSourceAccountSheet = ref<boolean>(false);
const showDestinationAccountSheet = ref<boolean>(false); const showDestinationAccountSheet = ref<boolean>(false);
const showTransactionDateTimeSheet = ref<boolean>(false); const showTransactionDateTimeSheet = ref<boolean>(false);
const showTransactionScheduledFrequencySheet = ref<boolean>(false); const showTransactionScheduledFrequencySheet = ref<boolean>(false);
const showScheduledStartDateSheet = ref<boolean>(false);
const showScheduledEndDateSheet = ref<boolean>(false);
const showGeoLocationMapSheet = ref<boolean>(false); const showGeoLocationMapSheet = ref<boolean>(false);
const showTransactionTagSheet = ref<boolean>(false); const showTransactionTagSheet = ref<boolean>(false);
const showTransactionPictures = ref<boolean>(false); const showTransactionPictures = ref<boolean>(false);
@@ -700,6 +731,34 @@ const transactionDisplayScheduledFrequency = computed<string>(() => {
} }
}); });
const transactionDisplayScheduledStartDate = computed<string>(() => {
if (pageTypeAndMode?.type !== TransactionEditPageType.Template) {
return '';
}
const template = transaction.value as TransactionTemplate;
if (template.scheduledStartDate) {
return formatDateToLongDate(template.scheduledStartDate);
} else {
return tt('Unspecified');
}
});
const transactionDisplayScheduledEndDate = computed<string>(() => {
if (pageTypeAndMode?.type !== TransactionEditPageType.Template) {
return '';
}
const template = transaction.value as TransactionTemplate;
if (template.scheduledEndDate) {
return formatDateToLongDate(template.scheduledEndDate);
} else {
return tt('Unspecified');
}
});
function getPageTypeNameMode(): { type: TransactionEditPageType, mode: TransactionEditPageMode } | null { function getPageTypeNameMode(): { type: TransactionEditPageType, mode: TransactionEditPageMode } | null {
if (props.f7route.path === '/transaction/add') { if (props.f7route.path === '/transaction/add') {
return { return {