Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
330a3c1
leasing: scaffold tenfast adapter
momentiris Nov 5, 2025
841d302
WIP Tenfast auth - mcdonaldsförslaget
mittistormen Nov 10, 2025
b8dc5c5
Script to create-db for testing
mittistormen Nov 12, 2025
af18f9f
Feat:AVTAL-22 Create Parking Space in Tenfast (#174)
mittistormen Nov 18, 2025
4430c79
Changed to api key again
mittistormen Nov 18, 2025
83b6266
Tests for create lease
mittistormen Nov 18, 2025
6f89b8c
Fixed typescript error
mittistormen Nov 24, 2025
75b22e6
New lock-file for pnpm
mittistormen Dec 15, 2025
294189b
feat/AVTAL-22 Adjustments after schema changes in tenfast
mittistormen Dec 16, 2025
123535b
Feat:AVTAL-22 Create Parking Space in Tenfast (#174)
mittistormen Nov 18, 2025
68919c9
add terminate lease endpoint in core service
lalmqvist Dec 11, 2025
2240254
add endpoint and logic to leasing service and tenfast adapter
lalmqvist Dec 11, 2025
3fc8d2e
adjust error handling
lalmqvist Dec 16, 2025
30cf360
call tenfast api endpoint
lalmqvist Dec 16, 2025
a3ca045
get lease from tenfast
lalmqvist Dec 16, 2025
c8622dc
use new extras endpoint
lalmqvist Dec 16, 2025
0dcc1f3
fix rebase errors
lalmqvist Dec 17, 2025
f79f4aa
add adapter tests
lalmqvist Dec 17, 2025
9005f02
core and leasing service route tests
lalmqvist Dec 17, 2025
3b59276
fix prettier error
lalmqvist Dec 17, 2025
12535c4
remove redundant error
lalmqvist Dec 18, 2025
0c5ea6c
remove redundant logging
lalmqvist Dec 22, 2025
f2915fa
Avtal 43 Get invoice details from tenfast (#189)
henrikhenrikpersson Dec 17, 2025
d9f352d
AVTAL-24: Get Invoice from TenFAST (#185)
momentiris Dec 17, 2025
9cb9a53
type error fix
lalmqvist Dec 30, 2025
f312b91
add validation in core endpoint
lalmqvist Jan 14, 2026
618f5e4
update error handling
lalmqvist Jan 14, 2026
0b44e09
AVTAL-42: create/delete invoice rows (#198)
momentiris Jan 13, 2026
753e8f8
prettier fix
lalmqvist Jan 14, 2026
2d1552b
handle error if tenant email is missing
lalmqvist Jan 15, 2026
1e0c711
add response and request schemas
lalmqvist Jan 28, 2026
55315b3
use parseRequestBody middleware
lalmqvist Jan 28, 2026
3f198bc
update error handling
lalmqvist Jan 28, 2026
165e1b4
Feat/avtal 26 get rent for sok ledigt (#232)
mittistormen Jan 20, 2026
f8cc02a
AVTAL-41: rent row start end (#263)
momentiris Jan 21, 2026
d6958b1
Feat: AVTAL-71 Choose Template From Rental Object When Creating Lease…
mittistormen Jan 22, 2026
39dca6b
Fixes after rebase on main
mittistormen Feb 3, 2026
dc03cfe
More fixes after rebase
mittistormen Feb 3, 2026
020644a
Test fixes after rebase
mittistormen Feb 3, 2026
4a8f1e5
Fixed tests after rebase
mittistormen Feb 3, 2026
2eef2d4
AVTAL-55: Onecore Home Insurance (#309)
momentiris Feb 10, 2026
18d696e
Freshly generated api-types for property tree
mittistormen Feb 11, 2026
867d91e
update to adhere to mimer.nu api request to get leases
lalmqvist Jan 26, 2026
be6abe2
update tests
lalmqvist Jan 27, 2026
275bede
fix failing tests
lalmqvist Jan 27, 2026
757dd08
update rent field of RentalObject
lalmqvist Jan 27, 2026
f390b03
update zod types
lalmqvist Feb 3, 2026
d790912
update optionalDateFields tenfast
lalmqvist Feb 9, 2026
9a771ae
remove includerentalobject param
lalmqvist Feb 10, 2026
ce92a22
prettier fix
lalmqvist Feb 10, 2026
dda7af2
add braarea
lalmqvist Feb 11, 2026
a524fa5
Feat: AVTAL-80 Remove Option To Include RentRows On Lease (#346)
mittistormen Feb 16, 2026
c42d6e9
Changed lease type to simplesign
mittistormen Feb 17, 2026
59fb804
add preliminary-terminated status to filter
lalmqvist Feb 18, 2026
6a3373b
Update tenfast-adapter.ts
lalmqvist Feb 18, 2026
f5f6ddd
Update filters.ts
lalmqvist Feb 18, 2026
c00533c
update filters
lalmqvist Feb 18, 2026
bca9db2
Merge pull request #367 from Bostads-AB-Mimer/fix/missing-status-prel…
lalmqvist Feb 18, 2026
9002556
add pendingsignature status and use stage value
lalmqvist Feb 25, 2026
449f097
Merge branch 'epic/avtal-49-epic-borja-anvanda-tenfast-for-avtal' int…
lalmqvist Feb 25, 2026
855963e
update filter functions
lalmqvist Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ const leaseStatusFormatMap: Record<LeaseStatus, string> = {
[LeaseStatus.AboutToEnd]: 'Uppsagt',
[LeaseStatus.Ended]: 'Upphört',
[LeaseStatus.PreliminaryTerminated]: 'Preliminärt uppsagt',
[LeaseStatus.PendingSignature]: 'Väntar på signering',
}

const formatLeaseStatus = (v: LeaseStatus) => leaseStatusFormatMap[v]
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ const leaseStatusFormatMap: Record<LeaseStatus, string> = {
[LeaseStatus.AboutToEnd]: 'Uppsagt',
[LeaseStatus.Ended]: 'Upphört',
[LeaseStatus.PreliminaryTerminated]: 'Preliminärt uppsagt',
[LeaseStatus.PendingSignature]: 'Väntar på signering',
}

const formatLeaseStatus = (v: LeaseStatus) => leaseStatusFormatMap[v]
1 change: 1 addition & 0 deletions libs/types/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ enum LeaseStatus {
AboutToEnd = 2, // Uppsagt, kommer att upphöra
Ended = 3, // Upphört
PreliminaryTerminated = 4, // Preliminärt uppsagt
PendingSignature = 5, // Väntar på signering
}

enum ParkingSpaceType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,53 +9,76 @@ export type GetLeasesFilters = {
| 'about-to-end'
| 'ended'
| 'preliminary-terminated'
| 'pending-signature'
)[]
}

export function filterByStatus(
export const filterByStatus = (
leases: TenfastLease[],
statuses: GetLeasesFilters['status']
) {
) => {
const now = new Date()
const seenIds = new Set<string>()

return statuses.reduce<TenfastLease[]>(
(acc, status) =>
acc.concat(
leases.filter((l) =>
match(status)
leases.filter((l) => {
// Skip if we've already included this lease (by externalId/leaseId)
if (seenIds.has(l.externalId)) return false

const matches = match(status)
.with('current', () => isCurrentLease(l, now))
.with('upcoming', () => isUpcomingLease(l, now))
.with('about-to-end', () => isAboutToEndLease(l, now))
.with('ended', () => isEndedLease(l, now))
.with('preliminary-terminated', () => isPreliminaryTerminated(l))
.with('pending-signature', () => isPendingSignature(l))
.exhaustive()
)

if (matches) {
seenIds.add(l.externalId)
return true
}
return false
})
),
[]
)
}

function isCurrentLease(l: TenfastLease, now: Date) {
return (
l.startDate < now &&
(!l.endDate || l.endDate > now) &&
!isPreliminaryTerminated(l) &&
!l.cancellation.cancelled
)
const isCurrentLease = (l: TenfastLease, now: Date) => {
return l.startDate < now && !l.endDate && l.stage === 'signed'
}

function isUpcomingLease(l: TenfastLease, now: Date) {
return l.startDate >= now
const isUpcomingLease = (l: TenfastLease, now: Date) => {
return l.startDate >= now && l.stage === 'signed'
}

function isAboutToEndLease(l: TenfastLease, now: Date) {
return l.endDate && l.endDate >= now && !!l.cancellation.cancelled
const isAboutToEndLease = (l: TenfastLease, now: Date) => {
return l.endDate !== null && l.endDate >= now
}

function isEndedLease(l: TenfastLease, now: Date) {
return l.endDate && l.endDate < now
const isEndedLease = (l: TenfastLease, now: Date) => {
return (
(l.stage === 'cancelled' || l.stage === 'archived') &&
l.endDate !== null &&
l.endDate < now
)
}

export const isPreliminaryTerminated = (lease: TenfastLease): boolean => {
return !!lease.simplesignTermination?.sentAt && !lease.cancellation.cancelled
return (
lease.stage === 'requestedCancellation' ||
lease.stage === 'preliminaryCancellation'
)
}

// TODO: Verify that acceptedByHyresgast and start actually means pending signature.
export const isPendingSignature = (lease: TenfastLease): boolean => {
return (
lease.stage === 'signingInProgress' ||
lease.stage === 'acceptedByHyresgast' ||
lease.stage === 'start'
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export const TenfastLeaseSchema = z.object({
betalasForskott: z.boolean(),
vatEnabled: z.boolean(),
method: z.string(),
signed: z.boolean(),
file: z
.object({
key: z.string(),
Expand Down Expand Up @@ -233,6 +234,7 @@ export const TenfastLeaseSchema = z.object({
updatedAt: z.coerce.date(),
startInvoicingFrom: optionalDateField,
signedAt: optionalDateField, // When the lease was finalized as in tenant signed it or manually marked by mimer if offline sign.
stage: z.string(),
tags: z.array(z.unknown()),
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ const defaultFilters: GetLeasesFilters = {
'about-to-end',
'ended',
'preliminary-terminated',
'pending-signature',
],
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
[LeaseStatus.Upcoming]: 'upcoming',
[LeaseStatus.AboutToEnd]: 'abouttoend',
[LeaseStatus.Ended]: 'ended',
[LeaseStatus.PreliminaryTerminated]: 'preliminaryterminated',
[LeaseStatus.PendingSignature]: 'pendingsignature',
}

/** Maps normalized status keys to SQL WHERE conditions */
Expand Down Expand Up @@ -430,7 +432,7 @@
* Batch fetch contacts for a list of lease keys
* Returns a Map from leaseKey to array of ContactInfo
*/
const getContactsForLeases = async (

Check warning on line 435 in services/leasing/src/services/lease-service/adapters/xpand/lease-search-adapter.ts

View workflow job for this annotation

GitHub Actions / @onecore/leasing - Lint

'getContactsForLeases' is assigned a value but never used. Allowed unused vars must match /^_/u
leaseKeys: string[]
): Promise<Map<string, leasing.v1.ContactInfo[]>> => {
if (leaseKeys.length === 0) {
Expand Down
63 changes: 43 additions & 20 deletions services/leasing/src/services/lease-service/helpers/tenfast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,52 @@ import {
TenfastInvoiceRow,
TenfastRentalObject,
} from '../adapters/tenfast/schemas'
import { isPreliminaryTerminated } from '../adapters/tenfast/filters'
import {
isPreliminaryTerminated,
isPendingSignature,
} from '../adapters/tenfast/filters'

const calculateLeaseStatus = (
startDate: Date,
endDate: Date | null,
isPreliminaryTerminated: boolean
): LeaseStatus => {
// TODO: Verify this logic
const calculateLeaseStatus = (lease: TenfastLease): LeaseStatus => {
const today = new Date()
if (endDate && endDate < today) return LeaseStatus.Ended
if (startDate >= today) return LeaseStatus.Upcoming
if (isPreliminaryTerminated) return LeaseStatus.PreliminaryTerminated
if (endDate && endDate >= today) return LeaseStatus.AboutToEnd
const { startDate, endDate, stage } = lease

// Check pending signature first (unsigned leases)
if (isPendingSignature(lease)) return LeaseStatus.PendingSignature

// Check preliminary termination
if (isPreliminaryTerminated(lease)) return LeaseStatus.PreliminaryTerminated

// Check ended leases (must be cancelled/archived with past end date)
if (
(stage === 'cancelled' || stage === 'archived') &&
endDate &&
endDate < today
) {
return LeaseStatus.Ended
}

// Check about to end - any lease with a future end date
if (endDate && endDate >= today) {
return LeaseStatus.AboutToEnd
}

// Check upcoming (signed with future start date)
if (startDate >= today && stage === 'signed') {
return LeaseStatus.Upcoming
}

// Current lease (signed, started, no end date)
if (stage === 'signed' && startDate < today && !endDate) {
return LeaseStatus.Current
}

// Default fallback
return LeaseStatus.Current
}

function mapToOnecoreRentalObject(
const mapToOnecoreRentalObject = (
rentalObject: TenfastRentalObject
): RentalObject | undefined {
): RentalObject | undefined => {
// Only map if we have populated fields (not just a reference)
if (!rentalObject.postadress) {
return undefined
Expand Down Expand Up @@ -69,17 +96,13 @@ function mapToOnecoreRentalObject(
// lastDebitDate: Date | undefined // Sista betaldatum
// approvalDate: Date | undefined // När godkände mimer kontraktet?

export function mapToOnecoreLease(lease: TenfastLease): Lease {
export const mapToOnecoreLease = (lease: TenfastLease): Lease => {
return {
leaseId: lease.externalId,
leaseNumber: lease.externalId.split('/')[1],
leaseStartDate: lease.startDate,
leaseEndDate: lease.endDate ?? undefined,
status: calculateLeaseStatus(
lease.startDate,
lease.endDate,
isPreliminaryTerminated(lease)
),
status: calculateLeaseStatus(lease),
noticeGivenBy: lease.cancellation.cancelledByType ?? undefined,
noticeDate: lease.cancellation.handledAt ?? undefined,
noticeTimeTenant: lease.uppsagningstid,
Expand All @@ -100,7 +123,7 @@ export function mapToOnecoreLease(lease: TenfastLease): Lease {
}
}

export function mapToOnecoreRentRow(row: TenfastInvoiceRow): LeaseRentRow {
export const mapToOnecoreRentRow = (row: TenfastInvoiceRow): LeaseRentRow => {
return {
id: row._id,
amount: row.amount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,38 @@ describe(filterByStatus, () => {
const currentLease = factory.tenfastLease.build({
startDate: sub(new Date(), { days: 1 }),
endDate: null,
cancellation: {
cancelled: false,
doneAutomatically: false,
receivedCancellationAt: null,
notifiedAt: null,
handledAt: null,
handledBy: null,
preferredMoveOutDate: null,
},
simplesignTermination: undefined,
stage: 'signed',
signed: true,
})
const upcomingLease = factory.tenfastLease.build({
startDate: add(new Date(), { days: 1 }),
endDate: null,
stage: 'signed',
signed: true,
})
const aboutToEndLease = factory.tenfastLease.build({
startDate: sub(new Date(), { days: 1 }),
endDate: add(new Date(), { days: 1 }),
cancellation: {
cancelled: true,
doneAutomatically: false,
receivedCancellationAt: null,
notifiedAt: null,
handledAt: new Date(),
handledBy: 'admin-user-id',
preferredMoveOutDate: null,
},
simplesignTermination: undefined,
stage: 'cancelled',
signed: true,
})
const preliminaryTerminatedLease = factory.tenfastLease.build({
startDate: sub(new Date(), { days: 1 }),
endDate: add(new Date(), { days: 30 }),
cancellation: {
cancelled: false,
doneAutomatically: false,
receivedCancellationAt: null,
notifiedAt: null,
handledAt: null,
handledBy: null,
preferredMoveOutDate: null,
},
simplesignTermination: {
signatures: [],
sentAt: new Date(),
signedAt: null,
},
stage: 'requestedCancellation',
signed: true,
})
const endedLease = factory.tenfastLease.build({
startDate: sub(new Date(), { days: 1 }),
endDate: sub(new Date(), { days: 1 }),
stage: 'cancelled',
signed: true,
})
const pendingSignatureLease = factory.tenfastLease.build({
startDate: sub(new Date(), { days: 1 }),
endDate: null,
stage: 'signingInProgress',
signed: false,
})

const leases = [
Expand All @@ -66,6 +48,7 @@ describe(filterByStatus, () => {
aboutToEndLease,
preliminaryTerminatedLease,
endedLease,
pendingSignatureLease,
]

expect(filterByStatus(leases, ['current'])).toEqual([currentLease])
Expand All @@ -75,5 +58,8 @@ describe(filterByStatus, () => {
preliminaryTerminatedLease,
])
expect(filterByStatus(leases, ['ended'])).toEqual([endedLease])
expect(filterByStatus(leases, ['pending-signature'])).toEqual([
pendingSignatureLease,
])
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const TenfastLeaseFactory = Factory.define<TenfastLease>(
betalasForskott: false,
vatEnabled: false,
method: 'digital',
signed: true,
stage: 'signed',
file: {
key: `file-key-${sequence}`,
location: 'https://files.example.com/file.pdf',
Expand All @@ -36,6 +38,7 @@ export const TenfastLeaseFactory = Factory.define<TenfastLease>(
handledAt: null,
handledBy: null,
preferredMoveOutDate: null,
cancelledByType: null,
},
deposit: {
ekoNotifications: [],
Expand Down
Loading