use async_trait::async_trait;
use chrono::NaiveDate;
use sqlx::PgPool;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq)]
pub struct EmployeeStatutory {
pub ptkp: String,
pub has_npwp: bool,
pub bpjs_kesehatan_family: Option<i32>,
pub join_date: Option<NaiveDate>,
pub ter_category: Option<String>,
}
#[async_trait]
pub trait EmployeeStatutoryInputs: Send + Sync {
async fn statutory_inputs(&self, employee_id: Uuid) -> Result<Option<EmployeeStatutory>, sqlx::Error>;
}
pub struct PoolEmployeeStatutoryInputs {
pool: PgPool,
}
impl PoolEmployeeStatutoryInputs {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
}
#[derive(sqlx::FromRow)]
struct StatutoryRaw {
ptkp_override: Option<String>,
has_npwp: Option<bool>,
ter_category: Option<String>,
bpjs_kesehatan_family: Option<i32>,
join_date: Option<NaiveDate>,
has_spouse: bool,
children: i64,
}
#[async_trait]
impl EmployeeStatutoryInputs for PoolEmployeeStatutoryInputs {
async fn statutory_inputs(&self, employee_id: Uuid) -> Result<Option<EmployeeStatutory>, sqlx::Error> {
let row: Option<StatutoryRaw> = sqlx::query_as(
r#"SELECT
(SELECT t.ptkp_override::text
FROM employee.employee_taxes t
WHERE t.employee_id = e.id AND (t.metadata->>'deleted_at') IS NULL
LIMIT 1) AS ptkp_override,
(SELECT NULLIF(TRIM(t.npwp_number), '') IS NOT NULL
FROM employee.employee_taxes t
WHERE t.employee_id = e.id AND (t.metadata->>'deleted_at') IS NULL
LIMIT 1) AS has_npwp,
(SELECT t.ter_category::text
FROM employee.employee_taxes t
WHERE t.employee_id = e.id AND (t.metadata->>'deleted_at') IS NULL
LIMIT 1) AS ter_category,
(SELECT b.bpjs_kesehatan_family
FROM employee.employee_bpjs b
WHERE b.employee_id = e.id AND (b.metadata->>'deleted_at') IS NULL
LIMIT 1) AS bpjs_kesehatan_family,
(SELECT em.join_date
FROM employee.employments em
WHERE em.employee_id = e.id AND (em.metadata->>'deleted_at') IS NULL
ORDER BY em.join_date
LIMIT 1) AS join_date,
EXISTS (SELECT 1
FROM employee.employee_families f
WHERE f.employee_id = e.id
AND f.relationship = 'spouse'::family_relationship
AND (f.metadata->>'deleted_at') IS NULL) AS has_spouse,
(SELECT COUNT(*)
FROM employee.employee_families f
WHERE f.employee_id = e.id
AND f.relationship = 'child'::family_relationship
AND (f.metadata->>'deleted_at') IS NULL) AS children
FROM employee.employees e
WHERE e.id = $1
AND (e.metadata->>'deleted_at') IS NULL
LIMIT 1"#,
)
.bind(employee_id)
.fetch_optional(&self.pool)
.await?;
Ok(row.map(|r| {
let dependents = r.children.min(3);
let ptkp = r.ptkp_override.unwrap_or_else(|| {
if r.has_spouse {
format!("k{dependents}")
} else {
format!("tk{dependents}")
}
});
EmployeeStatutory {
ptkp,
has_npwp: r.has_npwp.unwrap_or(false),
bpjs_kesehatan_family: r.bpjs_kesehatan_family,
join_date: r.join_date,
ter_category: r.ter_category,
}
}))
}
}