use crate::prelude::*;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Builder, Getters, Setters)]
pub struct ProtoInvoiceInfo<Period: IsPeriod> {
#[getset(get = "pub", set_with = "pub")]
offset: TimestampedInvoiceNumber<Period>,
#[builder(default)]
#[getset(get = "pub", set = "pub")]
record_of_periods_off: RecordOfPeriodsOff<Period>,
#[getset(get = "pub", set_with = "pub")]
purchase_order: Option<PurchaseOrder>,
#[getset(get = "pub", set_with = "pub")]
footer_text: Option<FooterText>,
#[getset(get = "pub", set_with = "pub")]
emphasize_color_hex: Option<HexColor>,
}
impl<Period: IsPeriod> ProtoInvoiceInfo<Period> {
pub fn insert_period_off(&mut self, period: Period) {
let mut periods_off = self.record_of_periods_off.clone();
periods_off.insert(period);
self.set_record_of_periods_off(periods_off);
}
}
impl<Period: IsPeriod + HasSample> HasSample for ProtoInvoiceInfo<Period> {
fn sample() -> Self {
Self::builder()
.purchase_order(PurchaseOrder::sample())
.footer_text(FooterText::sample())
.emphasize_color_hex(HexColor::sample())
.offset(TimestampedInvoiceNumber::sample())
.record_of_periods_off(RecordOfPeriodsOff::default())
.build()
}
fn sample_other() -> Self {
Self::builder()
.purchase_order(PurchaseOrder::sample_other())
.footer_text(FooterText::sample_other())
.emphasize_color_hex(HexColor::sample_other())
.offset(TimestampedInvoiceNumber::sample_other())
.record_of_periods_off(RecordOfPeriodsOff::default())
.build()
}
}
impl<Period: IsPeriod> ProtoInvoiceInfo<Period> {
pub fn validate(&self) -> Result<()> {
if self.record_of_periods_off.contains(self.offset.period()) {
return Err(Error::OffsetPeriodMustNotBeInRecordOfPeriodsOff {
offset_period: format!("{:?}", self.offset.period()),
period_kind: type_name::<Period>(),
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
type Sut = ProtoInvoiceInfo<YearAndMonth>;
#[test]
fn equality() {
assert_eq!(Sut::sample(), Sut::sample());
assert_eq!(Sut::sample_other(), Sut::sample_other());
}
#[test]
fn inequality() {
assert_ne!(Sut::sample(), Sut::sample_other());
}
#[test]
fn test_advance() {
let date = Date::from_str("2025-05-31").unwrap();
let advanced = date.advance(&PaymentTerms::net30());
assert_eq!(advanced, Date::from_str("2025-06-30").unwrap());
}
#[test]
fn test_proto_invoice_info_validate_valid() {
let invoice_info = Sut::sample();
assert!(invoice_info.validate().is_ok());
}
#[test]
fn test_proto_invoice_info_validate_invalid() {
let month = YearAndMonth::may(2025);
let invoice_info = Sut::builder()
.offset(
TimestampedInvoiceNumber::builder()
.period(month)
.offset(237.into())
.build(),
)
.record_of_periods_off(RecordOfPeriodsOff::new([month]))
.purchase_order(PurchaseOrder::sample())
.build();
let result = invoice_info.validate();
assert!(result.is_err());
}
#[test]
fn test_insert_period_off() {
let mut invoice_info = Sut::sample();
let period = YearAndMonth::may(2025);
invoice_info.insert_period_off(period);
assert!(invoice_info.record_of_periods_off().contains(&period));
}
}