feat(property): convert registrationDate to Date object and add Jalali utilities
deploy-chabokan / deploy (push) Canceled after 0s

This commit is contained in:
2026-08-19 06:45:50 +03:30
parent 982b9e759f
commit 8bdd5af719
3 changed files with 185 additions and 1 deletions
+1 -1
View File
@@ -57,7 +57,7 @@ const propertySchema = new mongoose.Schema(
serialNumber: String, // شماره سریال چاپی سند serialNumber: String, // شماره سریال چاپی سند
provinceRegistry: String, provinceRegistry: String,
localRegistryOffice: String, localRegistryOffice: String,
registrationDate: String, // تاریخ ثبت سند registrationDate: Date, // تاریخ ثبت سند (MongoDB Date object)
// Owner Specifications (مشخصات مالک) // Owner Specifications (مشخصات مالک)
ownerName: String, ownerName: String,
+10
View File
@@ -5,6 +5,7 @@ const AppError = require('../../globalErrorHandler/AppError')
const { client } = require('../../setup/typesense') const { client } = require('../../setup/typesense')
const TsApiFeatures = require('../../utils/TsApiFeatures') const TsApiFeatures = require('../../utils/TsApiFeatures')
const { moveFromTempToMain, getSignedUrl } = require('../file/fileService') const { moveFromTempToMain, getSignedUrl } = require('../file/fileService')
const { normalizeToDate } = require('../../utils/jalali')
const getAllProperties = async () => { const getAllProperties = async () => {
return Property.find({}, null, null).populate('category').populate('status') return Property.find({}, null, null).populate('category').populate('status')
@@ -18,6 +19,10 @@ const createProperty = async (data) => {
data.documentUrls = await moveFromTempToMain(data.documentUrls, 'documents') data.documentUrls = await moveFromTempToMain(data.documentUrls, 'documents')
} }
if (data.registrationDate !== undefined) {
data.registrationDate = normalizeToDate(data.registrationDate)
}
if (data.propertyType === 'Land' || data.propertyType === 'زمین') { if (data.propertyType === 'Land' || data.propertyType === 'زمین') {
return Land.create(data) return Land.create(data)
} else if (data.propertyType === 'Building' || data.propertyType === 'ساختمان') { } else if (data.propertyType === 'Building' || data.propertyType === 'ساختمان') {
@@ -52,6 +57,10 @@ const updateProperty = async (propertyId, data) => {
data.documentUrls = await moveFromTempToMain(data.documentUrls, 'documents') data.documentUrls = await moveFromTempToMain(data.documentUrls, 'documents')
} }
if (data.registrationDate !== undefined) {
data.registrationDate = normalizeToDate(data.registrationDate)
}
const property = await Property.findOneAndUpdate({ _id: propertyId }, data, { new: true }) const property = await Property.findOneAndUpdate({ _id: propertyId }, data, { new: true })
if (!property) { if (!property) {
@@ -180,6 +189,7 @@ const searchPropertiesMongo = async (inputQuery = {}) => {
registrationPlate: prop.registrationPlate || '', registrationPlate: prop.registrationPlate || '',
uniqueId: prop.uniqueId || '', uniqueId: prop.uniqueId || '',
serialNumber: prop.serialNumber || '', serialNumber: prop.serialNumber || '',
registrationDate: prop.registrationDate,
coordinates: prop.location?.coordinates || [0, 0], coordinates: prop.location?.coordinates || [0, 0],
location: prop.location, location: prop.location,
createdAt: Math.floor(new Date(prop.createdAt || Date.now()).getTime() / 1000), createdAt: Math.floor(new Date(prop.createdAt || Date.now()).getTime() / 1000),
+174
View File
@@ -0,0 +1,174 @@
/**
* Jalali (Solar Hijri / Shamsi) calendar conversion utilities (Backend)
*/
const persianMonths = [
{ value: 1, label: 'فروردین' },
{ value: 2, label: 'اردیبهشت' },
{ value: 3, label: 'خرداد' },
{ value: 4, label: 'تیر' },
{ value: 5, label: 'مرداد' },
{ value: 6, label: 'شهریور' },
{ value: 7, label: 'مهر' },
{ value: 8, label: 'آبان' },
{ value: 9, label: 'آذر' },
{ value: 10, label: 'دی' },
{ value: 11, label: 'بهمن' },
{ value: 12, label: 'اسفند' }
]
function toEnglishDigits(str) {
if (str === null || str === undefined) return ''
return String(str)
.replace(/[۰-۹]/g, (d) => '۰۱۲۳۴۵۶۷۸۹'.indexOf(d))
.replace(/[٠-٩]/g, (d) => '٠١٢٣٤٥٦٧٨٩'.indexOf(d))
}
function jalaliToGregorian(jy, jm, jd) {
jy = Number(jy)
jm = Number(jm)
jd = Number(jd)
let gy = jy > 979 ? 1600 : 621
jy = jy > 979 ? jy - 979 : jy
let days =
365 * jy +
Math.floor(jy / 33) * 8 +
Math.floor(((jy % 33) + 3) / 4) +
78 +
jd +
(jm < 7 ? (jm - 1) * 31 : (jm - 7) * 30 + 186)
gy += 400 * Math.floor(days / 146097)
days %= 146097
if (days > 36524) {
gy += 100 * Math.floor(--days / 36524)
days %= 36524
if (days >= 365) days++
}
gy += 4 * Math.floor(days / 1461)
days %= 1461
if (days > 365) {
gy += Math.floor((days - 1) / 365)
days = (days - 1) % 365
}
let gd = days + 1
const isLeap = (gy % 4 === 0 && gy % 100 !== 0) || gy % 400 === 0
const sal_a = [0, 31, isLeap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
let gm
for (gm = 0; gm < 13; gm++) {
const v = sal_a[gm]
if (gd <= v) break
gd -= v
}
return { gy, gm, gd }
}
function gregorianToJalali(gy, gm, gd) {
gy = Number(gy)
gm = Number(gm)
gd = Number(gd)
const g_d_m = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
let jy = gy > 1600 ? 979 : 0
gy = gy > 1600 ? gy - 1600 : gy - 621
const gy2 = gm > 2 ? gy + 1 : gy
let days =
365 * gy +
Math.floor((gy2 + 3) / 4) -
Math.floor((gy2 + 99) / 100) +
Math.floor((gy2 + 399) / 400) -
80 +
gd +
g_d_m[gm - 1]
jy += 33 * Math.floor(days / 12053)
days %= 12053
jy += 4 * Math.floor(days / 1461)
days %= 1461
if (days > 365) {
jy += Math.floor((days - 1) / 365)
days = (days - 1) % 365
}
const jm = days < 186 ? 1 + Math.floor(days / 31) : 7 + Math.floor((days - 186) / 30)
const jd = 1 + (days < 186 ? days % 31 : (days - 186) % 30)
return { jy, jm, jd }
}
function jalaliToDate(jy, jm, jd) {
const y = parseInt(toEnglishDigits(jy), 10)
const m = parseInt(toEnglishDigits(jm), 10)
const d = parseInt(toEnglishDigits(jd), 10)
if (isNaN(y) || isNaN(m) || isNaN(d) || y <= 0 || m < 1 || m > 12 || d < 1 || d > 31) {
return null
}
const { gy, gm, gd } = jalaliToGregorian(y, m, d)
return new Date(Date.UTC(gy, gm - 1, gd, 12, 0, 0))
}
function parseDateToJalali(dateVal) {
if (!dateVal) return null
if (dateVal instanceof Date || (typeof dateVal === 'string' && (dateVal.includes('T') || dateVal.includes('-')))) {
const d = new Date(dateVal)
if (!isNaN(d.getTime())) {
return gregorianToJalali(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate())
}
}
if (typeof dateVal === 'string' && dateVal.includes('/')) {
const clean = toEnglishDigits(dateVal.trim())
const parts = clean.split('/')
if (parts.length === 3) {
const jy = parseInt(parts[0], 10)
const jm = parseInt(parts[1], 10)
const jd = parseInt(parts[2], 10)
if (!isNaN(jy) && !isNaN(jm) && !isNaN(jd)) {
return { jy, jm, jd }
}
}
}
return null
}
function normalizeToDate(input) {
if (!input) return null
if (input instanceof Date && !isNaN(input.getTime())) return input
if (typeof input === 'string') {
const trimmed = input.trim()
if (trimmed.includes('/')) {
const parts = toEnglishDigits(trimmed).split('/')
if (parts.length === 3) {
return jalaliToDate(parts[0], parts[1], parts[2])
}
}
const d = new Date(trimmed)
if (!isNaN(d.getTime())) return d
}
return null
}
module.exports = {
persianMonths,
toEnglishDigits,
jalaliToGregorian,
gregorianToJalali,
jalaliToDate,
parseDateToJalali,
normalizeToDate,
}