Working proof of concept

master
Griffiths Lott 3 years ago
commit ba21d0dada
  1. 2
      .gitignore
  2. 2048
      Cargo.lock
  3. 15
      Cargo.toml
  4. 20
      Cargo.toml.bak
  5. 120
      src/main.rs
  6. 50
      src/pymt_trans_table.rs

2
.gitignore vendored

@ -0,0 +1,2 @@
/target
*.env

2048
Cargo.lock generated

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…
Cancel
Save