koprogo_api/infrastructure/database/repositories/
contractor_evaluation_repository_impl.rs1use crate::application::error::AppError;
13use crate::application::ports::ContractorEvaluationRepository;
14use crate::domain::entities::{ContractorEvaluation, EvaluationScores};
15use crate::infrastructure::database::pool::DbPool;
16use async_trait::async_trait;
17use sqlx::Row;
18use uuid::Uuid;
19
20pub struct PostgresContractorEvaluationRepository {
21 pool: DbPool,
22}
23
24impl PostgresContractorEvaluationRepository {
25 pub fn new(pool: DbPool) -> Self {
26 Self { pool }
27 }
28
29 fn row_to_evaluation(row: &sqlx::postgres::PgRow) -> Result<ContractorEvaluation, AppError> {
30 let quality: i16 = row.get("score_quality");
31 let timeliness: i16 = row.get("score_timeliness");
32 let communication: i16 = row.get("score_communication");
33 let cost_compliance: i16 = row.get("score_cost_compliance");
34 let overall: i16 = row.get("score_overall");
35 Ok(ContractorEvaluation {
36 id: row.get("id"),
37 contractor_user_id: row.get("contractor_user_id"),
38 technical_spec_id: row.get("technical_spec_id"),
39 linked_ticket_ids: row.get("linked_ticket_ids"),
40 evaluator_user_id: row.get("evaluator_user_id"),
41 scores: EvaluationScores {
42 quality: quality as u8,
43 timeliness: timeliness as u8,
44 communication: communication as u8,
45 cost_compliance: cost_compliance as u8,
46 overall: overall as u8,
47 },
48 comment: row.get("comment"),
49 created_at: row.get("created_at"),
50 })
51 }
52}
53
54#[async_trait]
55impl ContractorEvaluationRepository for PostgresContractorEvaluationRepository {
56 async fn save(&self, e: &ContractorEvaluation) -> Result<(), AppError> {
57 sqlx::query(
58 r#"
59 INSERT INTO contractor_evaluations (
60 id, contractor_user_id, technical_spec_id, linked_ticket_ids,
61 evaluator_user_id,
62 score_quality, score_timeliness, score_communication,
63 score_cost_compliance, score_overall,
64 comment, created_at
65 )
66 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
67 "#,
68 )
69 .bind(e.id)
70 .bind(e.contractor_user_id)
71 .bind(e.technical_spec_id)
72 .bind(&e.linked_ticket_ids)
73 .bind(e.evaluator_user_id)
74 .bind(e.scores.quality as i16)
75 .bind(e.scores.timeliness as i16)
76 .bind(e.scores.communication as i16)
77 .bind(e.scores.cost_compliance as i16)
78 .bind(e.scores.overall as i16)
79 .bind(&e.comment)
80 .bind(e.created_at)
81 .execute(&self.pool)
82 .await
83 .map_err(|err| AppError::Database(err.to_string()))?;
84 Ok(())
85 }
86
87 async fn find_by_id(&self, id: Uuid) -> Result<Option<ContractorEvaluation>, AppError> {
88 let row = sqlx::query(
89 r#"
90 SELECT id, contractor_user_id, technical_spec_id, linked_ticket_ids,
91 evaluator_user_id,
92 score_quality, score_timeliness, score_communication,
93 score_cost_compliance, score_overall,
94 comment, created_at
95 FROM contractor_evaluations
96 WHERE id = $1
97 "#,
98 )
99 .bind(id)
100 .fetch_optional(&self.pool)
101 .await
102 .map_err(|e| AppError::Database(e.to_string()))?;
103
104 match row {
105 None => Ok(None),
106 Some(row) => Ok(Some(Self::row_to_evaluation(&row)?)),
107 }
108 }
109
110 async fn list_for_contractor(
111 &self,
112 contractor_user_id: Uuid,
113 ) -> Result<Vec<ContractorEvaluation>, AppError> {
114 let rows = sqlx::query(
115 r#"
116 SELECT id, contractor_user_id, technical_spec_id, linked_ticket_ids,
117 evaluator_user_id,
118 score_quality, score_timeliness, score_communication,
119 score_cost_compliance, score_overall,
120 comment, created_at
121 FROM contractor_evaluations
122 WHERE contractor_user_id = $1
123 ORDER BY created_at DESC
124 "#,
125 )
126 .bind(contractor_user_id)
127 .fetch_all(&self.pool)
128 .await
129 .map_err(|e| AppError::Database(e.to_string()))?;
130
131 rows.iter().map(Self::row_to_evaluation).collect()
132 }
133}