use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc, NaiveDate};
use rust_decimal::Decimal;
#[cfg(feature = "openapi")]
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
use crate::domain::entity::DeliveryNote;
use crate::domain::entity::AuditMetadata;
use crate::domain::entity::DocStatus;
use crate::domain::entity::GlPostingState;
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct CreateDeliveryNoteDto {
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(alias = "delivery_number")]
pub delivery_number: String,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "branch_id")]
pub branch_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(alias = "customer_id")]
pub customer_id: Uuid,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "source_so_id")]
pub source_so_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(alias = "warehouse_id")]
pub warehouse_id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
#[serde(alias = "posting_date")]
pub posting_date: NaiveDate,
#[serde(alias = "total_cogs")]
pub total_cogs: Decimal,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(alias = "cogs_account_id")]
pub cogs_account_id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(alias = "inventory_account_id")]
pub inventory_account_id: Uuid,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "transfer_id")]
pub transfer_id: Option<Uuid>,
#[serde(alias = "posting_state")]
pub posting_state: GlPostingState,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "journal_id")]
pub journal_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "accounting_post_id")]
pub accounting_post_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "posted_at")]
pub posted_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct UpdateDeliveryNoteDto {
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(alias = "delivery_number")]
pub delivery_number: String,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "branch_id")]
pub branch_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(alias = "customer_id")]
pub customer_id: Uuid,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "source_so_id")]
pub source_so_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(alias = "warehouse_id")]
pub warehouse_id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
#[serde(alias = "posting_date")]
pub posting_date: NaiveDate,
#[serde(alias = "total_cogs")]
pub total_cogs: Decimal,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(alias = "cogs_account_id")]
pub cogs_account_id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(alias = "inventory_account_id")]
pub inventory_account_id: Uuid,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "transfer_id")]
pub transfer_id: Option<Uuid>,
pub status: DocStatus,
#[serde(alias = "posting_state")]
pub posting_state: GlPostingState,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "journal_id")]
pub journal_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "accounting_post_id")]
pub accounting_post_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "posted_at")]
pub posted_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct PatchDeliveryNoteDto {
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "delivery_number")]
pub delivery_number: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "branch_id")]
pub branch_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "customer_id")]
pub customer_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "source_so_id")]
pub source_so_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "warehouse_id")]
pub warehouse_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "posting_date")]
pub posting_date: Option<NaiveDate>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_cogs")]
pub total_cogs: Option<Decimal>,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "cogs_account_id")]
pub cogs_account_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "inventory_account_id")]
pub inventory_account_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "transfer_id")]
pub transfer_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<DocStatus>,
#[serde(skip_serializing_if = "Option::is_none", alias = "posting_state")]
pub posting_state: Option<GlPostingState>,
#[serde(skip_serializing_if = "Option::is_none", alias = "journal_id")]
pub journal_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "accounting_post_id")]
pub accounting_post_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "posted_at")]
pub posted_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
}
impl PatchDeliveryNoteDto {
pub fn has_changes(&self) -> bool {
self.delivery_number.is_some() || self.branch_id.is_some() || self.customer_id.is_some() || self.source_so_id.is_some() || self.warehouse_id.is_some() || self.posting_date.is_some() || self.total_cogs.is_some() || self.cogs_account_id.is_some() || self.inventory_account_id.is_some() || self.transfer_id.is_some() || self.status.is_some() || self.posting_state.is_some() || self.journal_id.is_some() || self.accounting_post_id.is_some() || self.posted_at.is_some() || self.notes.is_some()
}
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct DeliveryNoteResponseDto {
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
pub id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub delivery_number: String,
pub branch_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
pub customer_id: Uuid,
pub source_so_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
pub warehouse_id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
pub posting_date: NaiveDate,
pub total_cogs: Decimal,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
pub cogs_account_id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
pub inventory_account_id: Uuid,
pub transfer_id: Option<Uuid>,
pub status: DocStatus,
pub posting_state: GlPostingState,
pub journal_id: Option<Uuid>,
pub accounting_post_id: Option<Uuid>,
pub posted_at: Option<DateTime<Utc>>,
pub notes: Option<String>,
pub metadata: AuditMetadata,
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct DeliveryNoteListResponseDto {
pub items: Vec<DeliveryNoteResponseDto>,
pub total: u64,
pub page: u32,
pub per_page: u32,
pub total_pages: u32,
pub has_next: bool,
pub has_prev: bool,
}
impl DeliveryNoteListResponseDto {
pub fn new(items: Vec<DeliveryNoteResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
let total_pages = if per_page > 0 {
((total as f64) / (per_page as f64)).ceil() as u32
} else {
0
};
Self {
items,
total,
page,
per_page,
total_pages,
has_next: page < total_pages,
has_prev: page > 1,
}
}
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct DeliveryNoteSummaryDto {
pub id: Uuid,
pub delivery_number: String,
pub branch_id: Option<Uuid>,
pub customer_id: Uuid,
pub created_at: Option<DateTime<Utc>>,
}
impl From<DeliveryNote> for DeliveryNoteResponseDto {
fn from(entity: DeliveryNote) -> Self {
Self {
id: entity.id,
delivery_number: entity.delivery_number,
branch_id: entity.branch_id,
customer_id: entity.customer_id,
source_so_id: entity.source_so_id,
warehouse_id: entity.warehouse_id,
posting_date: entity.posting_date,
total_cogs: entity.total_cogs,
cogs_account_id: entity.cogs_account_id,
inventory_account_id: entity.inventory_account_id,
transfer_id: entity.transfer_id,
status: entity.status,
posting_state: entity.posting_state,
journal_id: entity.journal_id,
accounting_post_id: entity.accounting_post_id,
posted_at: entity.posted_at,
notes: entity.notes,
metadata: entity.metadata,
}
}
}
impl From<DeliveryNote> for DeliveryNoteSummaryDto {
fn from(entity: DeliveryNote) -> Self {
let created_at = backbone_core::PersistentEntity::created_at(&entity);
Self {
id: entity.id,
delivery_number: entity.delivery_number,
branch_id: entity.branch_id,
customer_id: entity.customer_id,
created_at,
}
}
}
impl From<CreateDeliveryNoteDto> for DeliveryNote {
fn from(dto: CreateDeliveryNoteDto) -> Self {
Self {
id: Uuid::new_v4(),
delivery_number: dto.delivery_number,
branch_id: dto.branch_id,
customer_id: dto.customer_id,
source_so_id: dto.source_so_id,
warehouse_id: dto.warehouse_id,
posting_date: dto.posting_date,
total_cogs: dto.total_cogs,
cogs_account_id: dto.cogs_account_id,
inventory_account_id: dto.inventory_account_id,
transfer_id: dto.transfer_id,
status: DocStatus::Draft,
posting_state: dto.posting_state,
journal_id: dto.journal_id,
accounting_post_id: dto.accounting_post_id,
posted_at: dto.posted_at,
notes: dto.notes,
metadata: AuditMetadata::default(),
}
}
}
impl From<&DeliveryNote> for DeliveryNoteResponseDto {
fn from(entity: &DeliveryNote) -> Self {
Self {
id: entity.id.clone(),
delivery_number: entity.delivery_number.clone(),
branch_id: entity.branch_id.clone(),
customer_id: entity.customer_id.clone(),
source_so_id: entity.source_so_id.clone(),
warehouse_id: entity.warehouse_id.clone(),
posting_date: entity.posting_date.clone(),
total_cogs: entity.total_cogs.clone(),
cogs_account_id: entity.cogs_account_id.clone(),
inventory_account_id: entity.inventory_account_id.clone(),
transfer_id: entity.transfer_id.clone(),
status: entity.status.clone(),
posting_state: entity.posting_state.clone(),
journal_id: entity.journal_id.clone(),
accounting_post_id: entity.accounting_post_id.clone(),
posted_at: entity.posted_at.clone(),
notes: entity.notes.clone(),
metadata: entity.metadata.clone(),
}
}
}
impl backbone_core::FromCreateDto<CreateDeliveryNoteDto> for DeliveryNote {
fn from_create_dto(dto: CreateDeliveryNoteDto) -> backbone_core::ServiceResult<Self> {
Ok(DeliveryNote::from(dto))
}
}
impl backbone_core::ApplyUpdateDto<UpdateDeliveryNoteDto> for DeliveryNote {
fn apply_update(mut self, dto: UpdateDeliveryNoteDto) -> backbone_core::ServiceResult<Self> {
self.delivery_number = dto.delivery_number;
self.branch_id = dto.branch_id;
self.customer_id = dto.customer_id;
self.source_so_id = dto.source_so_id;
self.warehouse_id = dto.warehouse_id;
self.posting_date = dto.posting_date;
self.total_cogs = dto.total_cogs;
self.cogs_account_id = dto.cogs_account_id;
self.inventory_account_id = dto.inventory_account_id;
self.transfer_id = dto.transfer_id;
self.status = dto.status;
self.posting_state = dto.posting_state;
self.journal_id = dto.journal_id;
self.accounting_post_id = dto.accounting_post_id;
self.posted_at = dto.posted_at;
self.notes = dto.notes;
Ok(self)
}
}