commit
ba21d0dada
@ -0,0 +1,2 @@ |
|||||||
|
/target |
||||||
|
*.env |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,15 @@ |
|||||||
|
[package] |
||||||
|
name = "FSS" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
tiberius = {version = "0.11.2", features = ["winauth", "rust_decimal", "chrono"]} |
||||||
|
tokio = {version = "1.21.2", features = ["full"]} |
||||||
|
tokio-util = {version = "0.7.4", features = ["full"]} |
||||||
|
connectorx = {version = "0.2.5"} |
||||||
|
polars = {version = "0.24.3"} |
||||||
|
dotenv = "0.15.0" |
||||||
|
|
||||||
@ -0,0 +1,20 @@ |
|||||||
|
[package] |
||||||
|
name = "FSS" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
tiberius = {version = "0.11.2", features = ["winauth", "rust_decimal", "chrono"]} |
||||||
|
tokio = {version = "1.21.2", features = ["full"]} |
||||||
|
tokio-util = {version = "0.7.4", features = ["full"]} |
||||||
|
chrono = {version = "0.4.22", features = ["serde"]} |
||||||
|
actix-web = "4" |
||||||
|
serde = {version = "1.0.147", features = ["derive"]} |
||||||
|
serde_json = "1.0.87" |
||||||
|
walkdir = "2.3.2" |
||||||
|
|
||||||
|
[target.x86_64-pc-windows-gnu] |
||||||
|
linker = "/usr/bin/$ARCH-w64-mingw32-gcc" |
||||||
|
ar = "/usr/$ARCH-w64-mingw32/bin/ar" |
||||||
@ -0,0 +1,120 @@ |
|||||||
|
use chrono::NaiveDateTime; |
||||||
|
use tiberius::{Client, Config, Query, AuthMethod, Row}; |
||||||
|
use tokio::net::TcpStream; |
||||||
|
use tokio_util::compat::{TokioAsyncWriteCompatExt, Compat}; |
||||||
|
mod pymt_trans_table; |
||||||
|
use pymt_trans_table::{PaymentData, FromSqlRow}; |
||||||
|
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, dev::Payload}; |
||||||
|
use serde::{Serialize, Deserialize}; |
||||||
|
use dotenv::dotenv; |
||||||
|
use std::env::var; |
||||||
|
|
||||||
|
|
||||||
|
type BDynError = Box< dyn std::error::Error>; |
||||||
|
|
||||||
|
#[actix_web::main] |
||||||
|
async fn main() -> Result<(), BDynError> { |
||||||
|
HttpServer::new(|| { |
||||||
|
App::new() |
||||||
|
.service(search_contract) |
||||||
|
}) |
||||||
|
.bind(("127.0.0.1", 8080))? |
||||||
|
.run() |
||||||
|
.await; |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Deserialize)] |
||||||
|
struct ContractNumber { |
||||||
|
contract_number: String |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Serialize)] |
||||||
|
struct SearchResponse { |
||||||
|
pub paid_in_full : Vec<PaymentData>, |
||||||
|
pub checks: Option<Vec<[String;2]>> |
||||||
|
} |
||||||
|
|
||||||
|
#[get("/contract/{contract_number}")] |
||||||
|
async fn search_contract(contract_number: web::Path<String>) -> HttpResponse { |
||||||
|
let mut resp = SearchResponse{paid_in_full: vec![], checks: None}; |
||||||
|
dotenv().ok(); |
||||||
|
let mut config = Config::new(); |
||||||
|
config.host("172.16.181.143"); |
||||||
|
config.port(1433); |
||||||
|
config.authentication(AuthMethod::windows(var("USER_NAME").except("No USER_NAME set in .env"), var("PASSWORD").except("No PASSWORD set in .env"))); |
||||||
|
config.trust_cert(); // on production, it is not a good idea to do this
|
||||||
|
|
||||||
|
let paid_in_full = get_paid_in_full(config.clone(), contract_number.to_string()).await; |
||||||
|
let mut checks = vec![]; |
||||||
|
match paid_in_full { |
||||||
|
Ok(pif) => { |
||||||
|
if pif.len() > 0 { |
||||||
|
for payment in pif { |
||||||
|
if payment.is_check() { |
||||||
|
checks.push(payment.check_info()); |
||||||
|
} |
||||||
|
resp.paid_in_full.push(payment); |
||||||
|
} |
||||||
|
let found_checks = find_checks(config, checks).await; |
||||||
|
match found_checks { |
||||||
|
Ok(check_list) => {resp.checks = Some(check_list);}, |
||||||
|
Err(e) => {return HttpResponse::NotFound().body("Contract not found")} |
||||||
|
} |
||||||
|
}; |
||||||
|
}, |
||||||
|
Err(e) => {return HttpResponse::NotFound().body("Contract not found")} |
||||||
|
} |
||||||
|
HttpResponse::Ok().body(serde_json::json!(resp).to_string()) |
||||||
|
} |
||||||
|
|
||||||
|
async fn create_sql_conn(config: Config) -> Result<Client<Compat<TcpStream>>, BDynError> { |
||||||
|
let tcp = TcpStream::connect(config.get_addr()).await?; |
||||||
|
tcp.set_nodelay(true)?; |
||||||
|
|
||||||
|
Ok(Client::connect(config, tcp.compat_write()).await?) |
||||||
|
} |
||||||
|
|
||||||
|
fn create_table_struct<T>(sql_result: Vec<Row>) -> Result<Vec<T>, BDynError> |
||||||
|
where T: FromSqlRow<T> |
||||||
|
{ |
||||||
|
let mut table: Vec<T> = vec![]; |
||||||
|
for row in sql_result { |
||||||
|
let raw_data = T::from_sql_row(row); |
||||||
|
match raw_data { |
||||||
|
Ok(structure_data) => table.push(structure_data), |
||||||
|
Err(e) => {} |
||||||
|
} |
||||||
|
} |
||||||
|
Ok(table) |
||||||
|
} |
||||||
|
|
||||||
|
async fn get_paid_in_full(config: Config, contract_number: String) -> Result<Vec<PaymentData>, BDynError> { |
||||||
|
let mut client = create_sql_conn(config).await?; |
||||||
|
let pif_query = Query::new(format!("USE NLCF SELECT TRXDSCRN as ContractNumber, BACHNUMB, VENDORID, PRCHAMNT, DOCDATE, DEX_ROW_TS FROM PM30200 WHERE TRXDSCRN = '{}'", contract_number)); |
||||||
|
let stream = pif_query.query(&mut client).await?; |
||||||
|
let row = stream.into_first_result().await?; |
||||||
|
create_table_struct(row) |
||||||
|
} |
||||||
|
|
||||||
|
async fn find_checks(config: Config, search_data: Vec<(String, String, NaiveDateTime)>) -> Result<Vec<[String;2]>, BDynError>{ |
||||||
|
use walkdir::{WalkDir, DirEntry}; |
||||||
|
let mut client = create_sql_conn(config).await?; |
||||||
|
let mut found_checks: Vec<[String;2]> = vec![]; |
||||||
|
|
||||||
|
for (vendor_id, bach_num, timestamp) in search_data { |
||||||
|
let check_query = Query::new(format!("USE NLCF SELECT VENDORID, CHEKNUMB, CHEKAMNT FROM ME240461 |
||||||
|
WHERE VENDORID = '{}' AND BACHNUMB= '{}' AND DOCDATE >= {}", vendor_id, bach_num, timestamp.format("%Y-%m-%d"))); |
||||||
|
let stream = check_query.query(&mut client).await?; |
||||||
|
let rows = stream.into_first_result().await?; |
||||||
|
if rows.len() < 1 {continue} |
||||||
|
for row in rows { |
||||||
|
let check_number: &str = row.get("CHEKNUMB").unwrap(); |
||||||
|
let vendor: &str = row.get("VENDORID").unwrap(); |
||||||
|
let files: Vec<DirEntry> = WalkDir::new(&format!("\\\\LCCPHL-SSFSH001\\Mekorma PDF\\NLCF\\PROCESSED\\*{}*",check_number)).into_iter().filter_map(|e| e.ok()).collect(); |
||||||
|
if files.len() > 0 {found_checks.push([vendor.to_string(), files[0].file_name().to_str().unwrap().to_string()])} |
||||||
|
} |
||||||
|
} |
||||||
|
Ok(found_checks) |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,50 @@ |
|||||||
|
|
||||||
|
use tiberius::{Row, numeric::Decimal}; |
||||||
|
use chrono::NaiveDateTime; |
||||||
|
use serde::{Serialize}; |
||||||
|
|
||||||
|
type BDynError = Box< dyn std::error::Error>; |
||||||
|
|
||||||
|
#[derive(Debug, Serialize)] |
||||||
|
pub struct PaymentData { |
||||||
|
contract_number: String, |
||||||
|
bach_num: String, |
||||||
|
vendor_id: String, |
||||||
|
purchase_amount: Decimal, |
||||||
|
doc_date: NaiveDateTime, |
||||||
|
dex_row_ts: NaiveDateTime, |
||||||
|
} |
||||||
|
impl PaymentData { |
||||||
|
pub fn is_check(&self) -> bool { |
||||||
|
self.bach_num.find("CHK").is_some() |
||||||
|
} |
||||||
|
pub fn check_info(&self) -> (String, String, NaiveDateTime) { |
||||||
|
return (self.vendor_id.clone(), self.bach_num.clone(), self.dex_row_ts.clone()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl FromSqlRow<PaymentData> for PaymentData { |
||||||
|
|
||||||
|
fn from_sql_row(sql_data: Row) -> Result<Self, BDynError> { |
||||||
|
let cn: &str = sql_data.get("ContractNumber").unwrap(); |
||||||
|
let bn: &str = sql_data.get("BACHNUMB").unwrap(); |
||||||
|
let vi: &str = sql_data.get("VENDORID").unwrap(); |
||||||
|
let pa: Decimal = sql_data.get("PRCHAMNT").unwrap(); |
||||||
|
let dd: NaiveDateTime = sql_data.get("DOCDATE").unwrap(); |
||||||
|
let dr: NaiveDateTime = sql_data.get("DEX_ROW_TS").unwrap(); |
||||||
|
|
||||||
|
Ok(PaymentData { |
||||||
|
contract_number: cn.to_string(), |
||||||
|
bach_num: bn.to_string(), |
||||||
|
vendor_id: vi.to_string(), |
||||||
|
purchase_amount: pa, |
||||||
|
doc_date: dd, |
||||||
|
dex_row_ts: dr, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub trait FromSqlRow<T> { |
||||||
|
fn from_sql_row(sql_data: Row) -> Result<T, BDynError>; |
||||||
|
} |
||||||
|
|
||||||
Loading…
Reference in new issue