Coverage for src/kwai/modules/club/import_members.py: 93%

73 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2024-01-01 00:00 +0000

1"""Module that implements a use case for importing members.""" 

2 

3from abc import ABC 

4from dataclasses import dataclass 

5 

6from kwai.core.domain.entity import Entity 

7from kwai.core.domain.presenter import Presenter 

8from kwai.core.domain.use_case import UseCaseResult 

9from kwai.modules.club.domain.file_upload import FileUploadEntity 

10from kwai.modules.club.domain.member import MemberEntity 

11from kwai.modules.club.repositories import member_importer 

12from kwai.modules.club.repositories.file_upload_repository import FileUploadRepository 

13from kwai.modules.club.repositories.member_repository import ( 

14 MemberNotFoundException, 

15 MemberRepository, 

16) 

17 

18 

19@dataclass(kw_only=True, slots=True, frozen=True) 

20class MemberImportResult(UseCaseResult, ABC): 

21 """The result of the use case ImportMembers.""" 

22 

23 file_upload: FileUploadEntity 

24 row: int 

25 

26 

27@dataclass(kw_only=True, slots=True, frozen=True) 

28class OkMemberImportResult(MemberImportResult): 

29 """A successful import of a member.""" 

30 

31 member: MemberEntity 

32 

33 def to_message(self) -> str: 

34 return f"Member {self.member.id}(row={self.row}) imported successfully." 

35 

36 

37@dataclass(kw_only=True, slots=True, frozen=True) 

38class FailureMemberImportResult(MemberImportResult): 

39 """An import of a member failed.""" 

40 

41 message: str 

42 

43 def to_message(self) -> str: 

44 return self.message 

45 

46 

47@dataclass(kw_only=True, slots=True, frozen=True) 

48class ImportMembersCommand: 

49 """Input for the use case "ImportMembers".""" 

50 

51 preview: bool = True 

52 

53 

54class ImportMembers: 

55 """Use case for importing members.""" 

56 

57 def __init__( 

58 self, 

59 importer: member_importer.MemberImporter, 

60 file_upload_repo: FileUploadRepository, 

61 member_repo: MemberRepository, 

62 presenter: Presenter, 

63 ): 

64 """Initialize the use case. 

65 

66 Args: 

67 importer: A class that is responsible for importing members from a resource. 

68 file_upload_repo: A repository for storing the file upload information. 

69 member_repo: A repository for managing members. 

70 presenter: A presenter 

71 """ 

72 self._importer = importer 

73 self._file_upload_repo = file_upload_repo 

74 self._member_repo = member_repo 

75 self._presenter = presenter 

76 

77 async def execute(self, command: ImportMembersCommand): 

78 """Execute the use case.""" 

79 file_upload_entity = await self._file_upload_repo.create( 

80 self._importer.create_file_upload_entity(command.preview) 

81 ) 

82 async for import_result in self._importer.import_(): 

83 match import_result: 

84 case member_importer.OkResult(): 

85 member = await self._save_member( 

86 file_upload_entity, import_result.member, command.preview 

87 ) 

88 self._presenter.present( 

89 OkMemberImportResult( 

90 file_upload=file_upload_entity, 

91 row=import_result.row, 

92 member=member, 

93 ) 

94 ) 

95 case member_importer.FailureResult(): 

96 self._presenter.present( 

97 FailureMemberImportResult( 

98 file_upload=file_upload_entity, 

99 row=import_result.row, 

100 message=import_result.message, 

101 ) 

102 ) 

103 if not command.preview: 

104 await self._activate_members(file_upload_entity) 

105 

106 async def _save_member( 

107 self, file_upload: FileUploadEntity, member: MemberEntity, preview: bool 

108 ) -> MemberEntity: 

109 """Create or update the member.""" 

110 existing_member = await self._get_member(member) 

111 if existing_member is not None: 

112 updated_member = self._update_member(existing_member, member) 

113 await self._file_upload_repo.save_member(file_upload, updated_member) 

114 if not preview: 

115 await self._member_repo.update(updated_member) 

116 return updated_member 

117 

118 if not preview: 

119 member = await self._member_repo.create(member) 

120 await self._file_upload_repo.save_member(file_upload, member) 

121 

122 return member 

123 

124 @classmethod 

125 def _update_member( 

126 cls, old_member: MemberEntity, new_member: MemberEntity 

127 ) -> MemberEntity: 

128 """Update an existing member with the new imported data.""" 

129 updated_contact = Entity.replace( 

130 old_member.person.contact, 

131 id_=old_member.person.contact.id, 

132 traceable_time=old_member.person.contact.traceable_time.mark_for_update(), 

133 ) 

134 updated_person = Entity.replace( 

135 old_member.person, 

136 id_=old_member.person.id, 

137 contact=updated_contact, 

138 traceable_time=old_member.person.traceable_time.mark_for_update(), 

139 ) 

140 updated_member = Entity.replace( 

141 new_member, 

142 id_=old_member.id, 

143 uuid=old_member.uuid, 

144 remark=old_member.remark, 

145 competition=old_member.is_competitive, 

146 active=old_member.is_active, 

147 person=updated_person, 

148 traceable_time=old_member.traceable_time.mark_for_update(), 

149 ) 

150 return updated_member 

151 

152 async def _get_member(self, member: MemberEntity) -> MemberEntity | None: 

153 """Return the member. 

154 

155 Returns: 

156 If found the member is returned, otherwise None is returned. 

157 """ 

158 member_query = self._member_repo.create_query() 

159 member_query.filter_by_license(member.license.number) 

160 

161 try: 

162 member = await self._member_repo.get(member_query) 

163 except MemberNotFoundException: 

164 return None 

165 

166 return member 

167 

168 async def _activate_members(self, upload_entity: FileUploadEntity): 

169 """Activate members. 

170 

171 Members that are part of the upload will be activated. 

172 Members not part of the upload will be deactivated. 

173 """ 

174 await self._member_repo.activate_members(upload_entity) 

175 await self._member_repo.deactivate_members(upload_entity)