Intermediate build. Start vetcove structure change

vetcove-upload
Wes Holland 5 months ago
parent 801d37202e
commit 846d8d5dde

136
Cargo.lock generated

@ -17,18 +17,6 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
@ -623,6 +611,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
@ -814,27 +808,22 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.14.5"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
dependencies = [
"ahash",
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
[[package]]
name = "hashlink"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.14.5",
"hashbrown",
]
[[package]]
@ -961,7 +950,7 @@ dependencies = [
"reqwest_cookie_store",
"serde",
"serde_json",
"thiserror",
"thiserror 1.0.65",
"tokio",
]
@ -1141,7 +1130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
"hashbrown 0.15.0",
"hashbrown",
]
[[package]]
@ -1162,6 +1151,7 @@ dependencies = [
"reqwest 0.12.9",
"ron",
"serde",
"serde_json",
"sqlx",
"tokio",
"tokio-stream",
@ -1446,7 +1436,7 @@ dependencies = [
"serde_json",
"serde_path_to_error",
"sha2",
"thiserror",
"thiserror 1.0.65",
"url",
]
@ -2209,21 +2199,11 @@ dependencies = [
"der",
]
[[package]]
name = "sqlformat"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790"
dependencies = [
"nom",
"unicode_categories",
]
[[package]]
name = "sqlx"
version = "0.8.2"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e"
checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
dependencies = [
"sqlx-core",
"sqlx-macros",
@ -2234,38 +2214,33 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.8.2"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e"
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
dependencies = [
"atoi",
"byteorder",
"base64 0.22.1",
"bytes",
"chrono",
"crc",
"crossbeam-queue",
"either",
"event-listener",
"futures-channel",
"futures-core",
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.14.5",
"hashbrown",
"hashlink",
"hex",
"indexmap",
"log",
"memchr",
"once_cell",
"paste",
"percent-encoding",
"serde",
"serde_json",
"sha2",
"smallvec",
"sqlformat",
"thiserror",
"thiserror 2.0.12",
"time",
"tokio",
"tokio-stream",
@ -2275,9 +2250,9 @@ dependencies = [
[[package]]
name = "sqlx-macros"
version = "0.8.2"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657"
checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
dependencies = [
"proc-macro2",
"quote",
@ -2288,9 +2263,9 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
version = "0.8.2"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5"
checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
dependencies = [
"dotenvy",
"either",
@ -2307,16 +2282,15 @@ dependencies = [
"sqlx-postgres",
"sqlx-sqlite",
"syn",
"tempfile",
"tokio",
"url",
]
[[package]]
name = "sqlx-mysql"
version = "0.8.2"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a"
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [
"atoi",
"base64 0.22.1",
@ -2350,7 +2324,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror",
"thiserror 2.0.12",
"time",
"tracing",
"whoami",
@ -2358,9 +2332,9 @@ dependencies = [
[[package]]
name = "sqlx-postgres"
version = "0.8.2"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8"
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [
"atoi",
"base64 0.22.1",
@ -2372,7 +2346,6 @@ dependencies = [
"etcetera",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"hex",
"hkdf",
@ -2390,7 +2363,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror",
"thiserror 2.0.12",
"time",
"tracing",
"whoami",
@ -2398,9 +2371,9 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
version = "0.8.2"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
dependencies = [
"atoi",
"chrono",
@ -2416,6 +2389,7 @@ dependencies = [
"serde",
"serde_urlencoded",
"sqlx-core",
"thiserror 2.0.12",
"time",
"tracing",
"url",
@ -2440,9 +2414,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.85"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
@ -2525,7 +2499,16 @@ version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.65",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.12",
]
[[package]]
@ -2539,6 +2522,17 @@ dependencies = [
"syn",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.8"
@ -2793,7 +2787,7 @@ dependencies = [
"rand",
"serde",
"serde_json",
"thiserror",
"thiserror 1.0.65",
"time",
"tokio",
"tracing",
@ -2820,7 +2814,7 @@ dependencies = [
"async-trait",
"rmp-serde",
"sqlx",
"thiserror",
"thiserror 1.0.65",
"time",
"tower-sessions-core",
]
@ -2932,12 +2926,6 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "untrusted"
version = "0.9.0"

@ -10,7 +10,7 @@ axum = { version = "0.7.7", features = ["macros", "multipart"] }
axum-htmx = { version = "0.6.0", features = ["auto-vary"] }
dotenvy = "0.15.7"
oauth2 = "4.4.2"
sqlx = { version = "0.8.2", default-features = false, features = ["runtime-tokio", "sqlite", "chrono", "macros"] }
sqlx = { version = "0.8.6", default-features = false, features = ["runtime-tokio", "sqlite", "chrono", "macros"] }
tokio = { version = "1.41.0", features = ["full", "tracing"] }
tower = { version = "0.5.1", features = ["util"] }
tower-http = { version = "0.6.1", features = ["fs", "trace"] }
@ -19,6 +19,7 @@ tower-sessions-sqlx-store = { version = "0.14.1", features = ["sqlite"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
serde = { version = "1.0.213", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "0.12.9", features = ["json"] }
askama_axum = "0.4.0"
axum-extra = "0.9.4"

@ -1,59 +1,59 @@
name,qty,unit,fractional,reorder,price
Amoxicillin/Clavulanate 62.5mg/ml 15ml,1,ct,false,10,25
Animax Ointment 15ml,1,ct,false,10,25
Buprenex 0.3mg/ml - Injectable,1,ct,false,10,25
Buprenex 0.3mg/ml Oral Solution,28,ct,false,10,25
Carprofen 100mg,168,ct,false,10,25
Carprofen 25mg,232,ct,false,10,25
Carprofen 75mg,80,ct,false,10,25
Cephalexin 250mg,605,ct,false,10,25
Cephalexin 500mg,513,ct,false,10,25
Cerenia 10mg/ml (Per ml),12,ml,true,10,25
Cough Tabs,325,ct,false,10,25
Cytopoint 10mg,2,ct,false,10,25
Cytopoint 20mg,0,ct,false,10,25
Cytopoint 30mg,4,ct,false,10,25
Cytopoint 40mg,6,ct,false,10,25
Dexamethasone 2mg/ml Injectable,99,ct,false,10,25
Dog Ends Starter Kit,1,ct,false,10,25
Doxycycline 100mg Tablet,104,ct,false,10,25
Elura 20mg/ml - 15ml,1,ct,false,10,25
Entyce 30mg/ml - 10ml bottle,1,ct,false,10,25
Fenbendazole 100mg/ml,900,ct,false,10,25
"Florfenicol, Terbinafine, Mometasone furoate Ear Treament 1ml Tube",68,ct,false,10,25
FortiFlora 30ct - Cat,2,ct,false,10,25
FortiFlora SA K9,3,ct,false,10,25
FVRCP TruFel Ultra Injectable,9,ct,false,10,25
Gabapentin 100mg,479,ct,false,10,25
Gabapentin 600mg Tablets,503,ct,false,10,25
Gentamicin Sulfate/Betamethasone Spray 60ml,0,ct,false,10,25
Kenalog 10mg/ml (Per ml),8.52,ml,true,10,25
Librela 10mg/ml,1,ct,false,10,25
Librela 20mg/ml,20,ct,false,10,25
Librela 30mg/ml,25,ct,false,10,25
Librela 5mg/ml,3,ct,false,10,25
Maropitant 16mg,10,ct,false,10,25
Maropitant 24mg,8,ct,false,10,25
Maropitant 60mg,16,ct,false,10,25
Meloxicam 1.5mg/ml - 10ml,2,ct,false,10,25
Metronidazole 125mg/ml - 30ML,0,ct,false,10,25
Metronidazole 250mg,15,ct,false,10,25
Mirataz 5g,3,ct,false,10,25
NeoPolyBac Ointment,1,ct,false,10,25
Nobivac DAPP,64,ct,false,10,25
Nobivac Intra-Trac Bordetella,68,ct,false,10,25
Nobivac Lepto 4,42,ct,false,10,25
Nobivac Rabies,96,ct,false,10,25
Ondansetron 8mg,16,ct,false,10,25
Praziquantel 56.8 mg/ml Injection,8,ct,false,10,25
Prednisolone 5mg,913,ct,false,10,25
Prednisone 20mg,259,ct,false,10,25
Pro-Pectalin 15ml,2,ct,false,10,25
Probiotic Powder Canine - 30ct,1,ct,false,10,25
PureVax Feline Rabies,25,ct,false,10,25
PureVax Recombinant FeLV,13,ct,false,10,25
Rilexine 150mg,12,ct,false,10,25
Rilexine 300mg,34,ct,false,10,25
Tobramycin Ophthalmic Sol 5ml,3,ct,false,10,25
Trazodone 100mg,257,ct,false,10,25
Varenzin-CA1 25mg/ml,2,ct,false,10,25
name,qty,unit,fractional,reorder,price,vetcove id
Amoxicillin/Clavulanate 62.5mg/ml 15ml,1,ct,false,10,25,628688
Animax Ointment 15ml,1,ct,false,10,25,143513
Buprenex 0.3mg/ml - Injectable,1,ct,false,10,25,
Buprenex 0.3mg/ml Oral Solution,28,ct,false,10,25,
Carprofen 100mg,168,ct,false,10,25,
Carprofen 25mg,232,ct,false,10,25,
Carprofen 75mg,80,ct,false,10,25,
Cephalexin 250mg,605,ct,false,10,25,
Cephalexin 500mg,513,ct,false,10,25,120077
Cerenia 10mg/ml (Per ml),12,ml,true,10,25,
Cough Tabs,325,ct,false,10,25,
Cytopoint 10mg,2,ct,false,10,25,
Cytopoint 20mg,0,ct,false,10,25,298905
Cytopoint 30mg,4,ct,false,10,25,
Cytopoint 40mg,6,ct,false,10,25,298499
Dexamethasone 2mg/ml Injectable,99,ct,false,10,25,
Dog Ends Starter Kit,1,ct,false,10,25,
Doxycycline 100mg Tablet,104,ct,false,10,25,
Elura 20mg/ml - 15ml,1,ct,false,10,25,
Entyce 30mg/ml - 10ml bottle,1,ct,false,10,25,
Fenbendazole 100mg/ml,900,ct,false,10,25,
"Florfenicol, Terbinafine, Mometasone furoate Ear Treament 1ml Tube",68,ct,false,10,25,
FortiFlora 30ct - Cat,2,ct,false,10,25,
FortiFlora SA K9,3,ct,false,10,25,
FVRCP TruFel Ultra Injectable,9,ct,false,10,25,
Gabapentin 100mg,479,ct,false,10,25,142264
Gabapentin 600mg Tablets,503,ct,false,10,25,11603
Gentamicin Sulfate/Betamethasone Spray 60ml,0,ct,false,10,25,
Kenalog 10mg/ml (Per ml),8.52,ml,true,10,25,
Librela 10mg/ml,1,ct,false,10,25,
Librela 20mg/ml,20,ct,false,10,25,605233
Librela 30mg/ml,25,ct,false,10,25,605231
Librela 5mg/ml,3,ct,false,10,25,
Maropitant 16mg,10,ct,false,10,25,
Maropitant 24mg,8,ct,false,10,25,
Maropitant 60mg,16,ct,false,10,25,
Meloxicam 1.5mg/ml - 10ml,2,ct,false,10,25,
Metronidazole 125mg/ml - 30ML,0,ct,false,10,25,
Metronidazole 250mg,15,ct,false,10,25,85774
Mirataz 5g,3,ct,false,10,25,
NeoPolyBac Ointment,1,ct,false,10,25,
Nobivac DAPP,64,ct,false,10,25,
Nobivac Intra-Trac Bordetella,68,ct,false,10,25,191649
Nobivac Lepto 4,42,ct,false,10,25,192695
Nobivac Rabies,96,ct,false,10,25,
Ondansetron 8mg,16,ct,false,10,25,
Praziquantel 56.8 mg/ml Injection,8,ct,false,10,25,
Prednisolone 5mg,913,ct,false,10,25,
Prednisone 20mg,259,ct,false,10,25,196593
Pro-Pectalin 15ml,2,ct,false,10,25,
Probiotic Powder Canine - 30ct,1,ct,false,10,25,
PureVax Feline Rabies,25,ct,false,10,25,433495
PureVax Recombinant FeLV,13,ct,false,10,25,546596
Rilexine 150mg,12,ct,false,10,25,192751
Rilexine 300mg,34,ct,false,10,25,
Tobramycin Ophthalmic Sol 5ml,3,ct,false,10,25,
Trazodone 100mg,257,ct,false,10,25,
Varenzin-CA1 25mg/ml,2,ct,false,10,25,

1 name qty unit fractional reorder price vetcove id
2 Amoxicillin/Clavulanate 62.5mg/ml 15ml 1 ct false 10 25 628688
3 Animax Ointment 15ml 1 ct false 10 25 143513
4 Buprenex 0.3mg/ml - Injectable 1 ct false 10 25
5 Buprenex 0.3mg/ml Oral Solution 28 ct false 10 25
6 Carprofen 100mg 168 ct false 10 25
7 Carprofen 25mg 232 ct false 10 25
8 Carprofen 75mg 80 ct false 10 25
9 Cephalexin 250mg 605 ct false 10 25
10 Cephalexin 500mg 513 ct false 10 25 120077
11 Cerenia 10mg/ml (Per ml) 12 ml true 10 25
12 Cough Tabs 325 ct false 10 25
13 Cytopoint 10mg 2 ct false 10 25
14 Cytopoint 20mg 0 ct false 10 25 298905
15 Cytopoint 30mg 4 ct false 10 25
16 Cytopoint 40mg 6 ct false 10 25 298499
17 Dexamethasone 2mg/ml Injectable 99 ct false 10 25
18 Dog Ends Starter Kit 1 ct false 10 25
19 Doxycycline 100mg Tablet 104 ct false 10 25
20 Elura 20mg/ml - 15ml 1 ct false 10 25
21 Entyce 30mg/ml - 10ml bottle 1 ct false 10 25
22 Fenbendazole 100mg/ml 900 ct false 10 25
23 Florfenicol, Terbinafine, Mometasone furoate Ear Treament 1ml Tube 68 ct false 10 25
24 FortiFlora 30ct - Cat 2 ct false 10 25
25 FortiFlora SA K9 3 ct false 10 25
26 FVRCP TruFel Ultra Injectable 9 ct false 10 25
27 Gabapentin 100mg 479 ct false 10 25 142264
28 Gabapentin 600mg Tablets 503 ct false 10 25 11603
29 Gentamicin Sulfate/Betamethasone Spray 60ml 0 ct false 10 25
30 Kenalog 10mg/ml (Per ml) 8.52 ml true 10 25
31 Librela 10mg/ml 1 ct false 10 25
32 Librela 20mg/ml 20 ct false 10 25 605233
33 Librela 30mg/ml 25 ct false 10 25 605231
34 Librela 5mg/ml 3 ct false 10 25
35 Maropitant 16mg 10 ct false 10 25
36 Maropitant 24mg 8 ct false 10 25
37 Maropitant 60mg 16 ct false 10 25
38 Meloxicam 1.5mg/ml - 10ml 2 ct false 10 25
39 Metronidazole 125mg/ml - 30ML 0 ct false 10 25
40 Metronidazole 250mg 15 ct false 10 25 85774
41 Mirataz 5g 3 ct false 10 25
42 NeoPolyBac Ointment 1 ct false 10 25
43 Nobivac DAPP 64 ct false 10 25
44 Nobivac Intra-Trac Bordetella 68 ct false 10 25 191649
45 Nobivac Lepto 4 42 ct false 10 25 192695
46 Nobivac Rabies 96 ct false 10 25
47 Ondansetron 8mg 16 ct false 10 25
48 Praziquantel 56.8 mg/ml Injection 8 ct false 10 25
49 Prednisolone 5mg 913 ct false 10 25
50 Prednisone 20mg 259 ct false 10 25 196593
51 Pro-Pectalin 15ml 2 ct false 10 25
52 Probiotic Powder Canine - 30ct 1 ct false 10 25
53 PureVax Feline Rabies 25 ct false 10 25 433495
54 PureVax Recombinant FeLV 13 ct false 10 25 546596
55 Rilexine 150mg 12 ct false 10 25 192751
56 Rilexine 300mg 34 ct false 10 25
57 Tobramycin Ophthalmic Sol 5ml 3 ct false 10 25
58 Trazodone 100mg 257 ct false 10 25
59 Varenzin-CA1 25mg/ml 2 ct false 10 25

@ -27,9 +27,6 @@ CREATE TABLE IF NOT EXISTS InventoryItem (
allow_fractional_units BOOLEAN NOT NULL,
active BOOLEAN NOT NULL,
pims_id TEXT,
vetcove_id TEXT,
manufacturer_name TEXT,
manufacturer_id TEXT,
FOREIGN KEY(display_unit) REFERENCES DisplayUnit(id)
);
@ -71,136 +68,46 @@ INSERT INTO DisplayUnit (id, unit, abbreviation) VALUES
(2,'milliliter', 'ml'),
(3,'milligram', 'mg');
CREATE TABLE IF NOT EXISTS SkuCovetrus
CREATE TABLE IF NOT EXISTS VetcoveItem
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuMWI
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuPatterson
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuMidwest
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuFirstVet
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuPennVet
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuAmatheon
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuVictor
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuVetcove
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuMillerVet
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuBoehringerIngelheim
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuMillerVet
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuZoetis
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuPharmasourceAH
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuNEAnimalHealth
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuDechra
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuMedline
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
vetcove_item_id INTEGER PRIMARY KEY NOT NULL,
item_name TEXT NOT NULL,
ignored INTEGER NOT NULL DEFAULT 0,
manufacturer_name TEXT,
manufacturer_number TEXT,
category TEXT,
units TEXT,
covetrus_sku TEXT,
mwi_sku TEXT,
patterson_sku TEXT,
midwest_sku TEXT,
first_vet_sku TEXT,
penn_vet_sku TEXT,
amatheon_sku TEXT,
victor_sku TEXT,
vetcove_sku TEXT,
miller_vet_sku TEXT,
boehringer_ingelheim_sku TEXT,
zoetis_sku TEXT,
pharmsource_ah_sku TEXT,
ne_animal_health_sku TEXT,
dechra_sku TEXT,
medline_sku TEXT,
elanco_sku TEXT
);
CREATE TABLE IF NOT EXISTS VetcoveItemToInventoryItemMap
(
vetcove_item_id INTEGER NOT NULL UNIQUE,
inventory_item_id INTEGER NOT NULL,
FOREIGN KEY(vetcove_item_id) REFERENCES VetcoveItem(vetcove_item_id)
FOREIGN KEY(inventory_item_id) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS SkuElanco
(
sku TEXT NOT NULL UNIQUE,
item INTEGER,
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS VetcoveItemHistory
(
id INTEGER PRIMARY KEY NOT NULL,
vetcove_entry_id INTEGER NOT NULL UNIQUE,
vetcove_history_entry_id INTEGER PRIMARY KEY NOT NULL,
vetcove_item_id INTEGER NOT NULL,
order_date TEXT NOT NULL,
item_name TEXT NOT NULL,
@ -223,10 +130,10 @@ CREATE TABLE IF NOT EXISTS VetcoveItemHistory
supplier_sku TEXT
);
CREATE TABLE IF NOT EXISTS VetcoveItemHistoryToAdjustmentMapping
CREATE TABLE IF NOT EXISTS VetcoveItemHistoryToAdjustmentMap
(
vetcove_history_entry_id INTEGER NOT NULL UNIQUE,
adjustment_id INTEGER NOT NULL UNIQUE,
vetcove_item_history_id INTEGER NOT NULL UNIQUE,
FOREIGN KEY(vetcove_history_entry_id) REFERENCES VetcoveItemHistory(vetcove_history_entry_id)
FOREIGN KEY(adjustment_id) REFERENCES Adjustment(id)
FOREIGN KEY(vetcove_item_history_id) REFERENCES VetcoveItemHistory(id)
);

@ -24,7 +24,6 @@ pub struct CreateItemFormTemplate {
pub display_unit_value: StringField,
pub reorder_point: StringField,
pub pims_id: StringField,
pub vetcove_id: StringField,
pub allow_fractional_units: BoolField,
}
@ -50,7 +49,6 @@ pub struct CreateItemFormData {
display_unit: String,
reorder_point: f64,
pims_id: Option<String>,
vetcove_id: Option<String>,
#[serde(default, deserialize_with = "deserialize_form_checkbox")]
allow_fractional_units: bool,
}
@ -80,16 +78,12 @@ impl ValidateForm for FormBase<CreateItemFormData> {
let pims_id = StringField::new(self.form_data.pims_id.clone().unwrap_or_default())
.invalid_if(|v| v.chars().any(char::is_whitespace), FieldError::ValidIdentifier);
let vetcove_id = StringField::new(self.form_data.vetcove_id.clone().unwrap_or_default())
.invalid_if(|v| !v.chars().all(|c| c.is_ascii_digit()), FieldError::ValidIdentifier);
let allow_fractional_units = BoolField::new(self.form_data.allow_fractional_units);
if name.is_error() ||
display_unit_value.is_error() ||
reorder_point.is_error() ||
pims_id.is_error() ||
vetcove_id.is_error() {
pims_id.is_error() {
return Err(ValidateFormError::ValidationError(
Self::ValidationErrorResponse {
@ -98,7 +92,6 @@ impl ValidateForm for FormBase<CreateItemFormData> {
display_unit_value,
reorder_point,
pims_id,
vetcove_id,
allow_fractional_units,
}));
}
@ -118,7 +111,7 @@ pub async fn create_item_form_post(
let _new_id = db::inventory_item::add_inventory_item(&state.db, &form_data.name, form_data.reorder_point,
form_data.allow_fractional_units, &form_data.display_unit,
&form_data.pims_id, &form_data.vetcove_id,
&form_data.pims_id,
).await?;
let fresh_form = CreateItemFormTemplate::base_response(&state).await?;

@ -26,7 +26,6 @@ pub struct EditItemFormTemplate {
pub display_unit_value: EditStringField,
pub reorder_point: EditStringField,
pub pims_id: EditStringField,
pub vetcove_id: EditStringField,
pub allow_fractional_units: EditBoolField,
}
@ -34,7 +33,6 @@ impl EditItemFormTemplate {
pub fn base(item: DbInventoryItemEditableFields, display_units: Vec<DbDisplayUnit>) -> Self {
let name = EditStringField::new(item.name);
let pims_id = EditStringField::from(item.pims_id);
let vetcove_id = EditStringField::from(item.vetcove_id);
let allow_fractional_units = EditBoolField::new(item.allow_fractional_units);
let display_unit_value = EditStringField::new(item.display_unit_abbreviation);
@ -47,7 +45,6 @@ impl EditItemFormTemplate {
name,
reorder_point,
pims_id,
vetcove_id,
allow_fractional_units,
display_unit_value,
display_units,
@ -107,12 +104,6 @@ impl ValidateForm for FormWithPathVars<i64,EditItemFormData> {
)
.invalid_if(|v| v.chars().any(char::is_whitespace), FieldError::ValidIdentifier);
let vetcove_id = EditStringField::new_with_base(
self.form_data.vetcove_id.clone().unwrap_or_default(),
current_values.vetcove_id.unwrap_or_default(),
)
.invalid_if(|v| !v.chars().all(|c| c.is_ascii_digit()), FieldError::ValidIdentifier);
let allow_fractional_units = EditBoolField::new_with_base(
self.form_data.allow_fractional_units,
current_values.allow_fractional_units,
@ -121,8 +112,7 @@ impl ValidateForm for FormWithPathVars<i64,EditItemFormData> {
if name.is_error() ||
display_unit_value.is_error() ||
reorder_point.is_error() ||
pims_id.is_error() ||
vetcove_id.is_error() {
pims_id.is_error() {
return Err(ValidateFormError::ValidationError(
Self::ValidationErrorResponse {
@ -132,7 +122,6 @@ impl ValidateForm for FormWithPathVars<i64,EditItemFormData> {
display_unit_value,
reorder_point,
pims_id,
vetcove_id,
allow_fractional_units,
}));
}
@ -153,7 +142,7 @@ pub async fn edit_item_form_post(
db::inventory_item::update_inventory_item(&state.db, id, &form_data.name, form_data.reorder_point,
form_data.allow_fractional_units, &form_data.display_unit,
&form_data.pims_id, &form_data.vetcove_id, true,
&form_data.pims_id, true,
).await?;
let new_base = db::inventory_item::inventory_item_get_by_id_editable_fields(&state.db, id).await?;

@ -48,6 +48,7 @@ pub fn routes() -> Router<AppState> {
.route("/upload", get(upload::index::upload_index_handler))
.route("/upload/catalog", post(upload::catalog::catalog_import))
.route("/upload/vetcove/history", post(upload::vetcove::item_history_import))
.route("/upload/vetcove/items", post(upload::vetcove::vetcove_item_import))
.route("/overview", get(overview::overview_handler))
.route("/reports", get(reports::reports_handler))
.route("/history", get(history::history_log_handler))

@ -3,7 +3,7 @@ use askama_axum::{IntoResponse, Response};
use crate::error::{AppError};
#[derive(Template)]
#[template(path = "upload.html")]
#[template(path = "upload/upload.html")]
struct UploadIndexTemplate;
pub async fn upload_index_handler() -> Result<Response, AppError> {

@ -2,19 +2,55 @@ use axum::extract::{Multipart, State};
use sqlx::SqlitePool;
use askama_axum::{IntoResponse, Response};
use anyhow::anyhow;
use askama::Template;
use axum::Json;
use tracing::info;
use crate::error::AppError;
use crate::session::SessionUser;
use crate::ingest::vetcove::ingest_item_history_bytes;
use crate::ingest::vetcove::{ingest_item_history_bytes, ItemHistoryImportRowResult, ItemHistoryImportRowErrorKind, VetcoveItemImportRowResult, ingest_vetcove_items_bytes};
#[derive(Template)]
#[template(path = "upload/vetcove-item-import-results.html")]
struct VetcoveItemImportResultsTemplate {
pub results: Vec<VetcoveItemImportRowResult>,
}
pub async fn vetcove_item_import(
State(db): State<SqlitePool>,
user: SessionUser,
mut multipart: Multipart,
) -> Result<Response, AppError> {
let mut results = vec![];
while let Some(field) = multipart.next_field().await? {
let filename = field.file_name().ok_or(anyhow!("field missing filename"))?.to_string();
let name = field.name().ok_or(anyhow!("field missing name"))?.to_string();
let content_type = field.content_type().ok_or(anyhow!("field missing content type"))?.to_string();
let data = field.bytes().await?;
info!("Name: {}, file: {}, content: {}, data: {} bytes", name, filename, content_type, data.len());
results = ingest_vetcove_items_bytes(data, db.clone(), user.id).await?;
}
Ok(VetcoveItemImportResultsTemplate { results }.into_response())
}
#[derive(Template)]
#[template(path = "upload/vetcove-history-import-results.html")]
struct ItemHistoryImportResultsTemplate {
pub results: Vec<ItemHistoryImportRowResult>,
}
pub async fn item_history_import(
State(db): State<SqlitePool>,
user: SessionUser,
mut multipart: Multipart,
) -> Result<Response, AppError> {
let mut filename = "".to_owned();
let mut results = vec![];
while let Some(field) = multipart.next_field().await? {
filename = field.file_name().ok_or(anyhow!("field missing filename"))?.to_string();
let filename = field.file_name().ok_or(anyhow!("field missing filename"))?.to_string();
let name = field.name().ok_or(anyhow!("field missing name"))?.to_string();
let content_type = field.content_type().ok_or(anyhow!("field missing content type"))?.to_string();
@ -22,7 +58,10 @@ pub async fn item_history_import(
info!("Name: {}, file: {}, content: {}, data: {} bytes", name, filename, content_type, data.len());
ingest_item_history_bytes(data, db.clone(), user.id).await?;
results = ingest_item_history_bytes(data, db.clone(), user.id).await?;
}
Ok(format!("File {} uploaded successfully", filename).into_response())
Ok(ItemHistoryImportResultsTemplate { results }.into_response())
}

@ -13,9 +13,6 @@ pub struct DbInventoryItem {
pub display_unit: i64,
pub active: bool,
pub pims_id: Option<String>,
pub vetcove_id: Option<String>,
pub manufacturer_name: Option<String>,
pub manufacturer_id: Option<String>,
}
@ -31,10 +28,7 @@ pub async fn inventory_item_get_all(db: &SqlitePool, page_size: i64, page_num: i
allow_fractional_units,
display_unit,
active,
pims_id,
vetcove_id,
manufacturer_name,
manufacturer_id
pims_id
FROM
InventoryItem
LIMIT ? OFFSET ?
@ -62,10 +56,7 @@ pub async fn inventory_item_get_search(db: &SqlitePool,
allow_fractional_units,
display_unit,
active,
pims_id,
vetcove_id,
manufacturer_name,
manufacturer_id
pims_id
FROM
InventoryItem
WHERE InventoryItem.name LIKE ?
@ -89,10 +80,7 @@ pub async fn inventory_item_get_by_id(db: &SqlitePool, id: i64) -> Result<DbInve
allow_fractional_units,
display_unit,
active,
pims_id,
vetcove_id,
manufacturer_name,
manufacturer_id
pims_id
FROM
InventoryItem
WHERE InventoryItem.id = ?
@ -105,14 +93,14 @@ pub async fn inventory_item_get_by_id(db: &SqlitePool, id: i64) -> Result<DbInve
pub async fn add_inventory_item(db: &SqlitePool, name: &str, reorder_point: f64,
allow_fractional_units: bool, display_unit_abbreviation: &str,
pims_id: &Option<String>, vetcove_id: &Option<String>,
pims_id: &Option<String>,
) -> Result<i64> {
let res = sqlx::query!(
r#"
INSERT INTO InventoryItem (name, reorder_point, allow_fractional_units, display_unit, active, pims_id, vetcove_id)
VALUES (?, ?, ?, (SELECT id from DisplayUnit WHERE abbreviation = ? ), ?, ?, ?)
INSERT INTO InventoryItem (name, reorder_point, allow_fractional_units, display_unit, active, pims_id)
VALUES (?, ?, ?, (SELECT id from DisplayUnit WHERE abbreviation = ? ), ?, ?)
"#,
name, reorder_point, allow_fractional_units, display_unit_abbreviation, true, pims_id, vetcove_id
name, reorder_point, allow_fractional_units, display_unit_abbreviation, true, pims_id
).execute(db).await?;
let new_id = res.last_insert_rowid();
@ -221,7 +209,6 @@ pub struct DbInventoryItemEditableFields {
pub display_unit_abbreviation: String,
pub active: bool,
pub pims_id: Option<String>,
pub vetcove_id: Option<String>,
}
pub async fn inventory_item_get_by_id_editable_fields(db: &SqlitePool, id: i64) -> Result<DbInventoryItemEditableFields> {
@ -237,8 +224,7 @@ pub async fn inventory_item_get_by_id_editable_fields(db: &SqlitePool, id: i64)
display_unit.unit as display_unit_str,
display_unit.abbreviation as display_unit_abbreviation,
item.active as active,
item.pims_id as pims_id,
item.vetcove_id as vetcove_id
item.pims_id as pims_id
FROM
InventoryItem as item
JOIN DisplayUnit as display_unit ON item.display_unit = display_unit.id
@ -252,7 +238,7 @@ pub async fn inventory_item_get_by_id_editable_fields(db: &SqlitePool, id: i64)
pub async fn update_inventory_item(db: &SqlitePool, id: i64, name: &str, reorder_point: f64,
allow_fractional_units: bool, display_unit_abbreviation: &str,
pims_id: &Option<String>, vetcove_id: &Option<String>, active: bool
pims_id: &Option<String>, active: bool
) -> Result<()> {
let affected = sqlx::query!(
r#"
@ -262,8 +248,7 @@ pub async fn update_inventory_item(db: &SqlitePool, id: i64, name: &str, reorder
allow_fractional_units = ?,
display_unit = (SELECT id from DisplayUnit WHERE abbreviation = ? ),
active = ?,
pims_id = ?,
vetcove_id = ?
pims_id = ?
WHERE id = ?
"#,
name,
@ -272,7 +257,6 @@ pub async fn update_inventory_item(db: &SqlitePool, id: i64, name: &str, reorder
display_unit_abbreviation,
active,
pims_id,
vetcove_id,
id
).execute(db).await?.rows_affected();

@ -0,0 +1,274 @@
use serde::Serialize;
use anyhow::Result;
use chrono::{DateTime, NaiveDate, Utc};
use sqlx::SqlitePool;
#[derive(Clone, Debug, Serialize)]
#[derive(sqlx::FromRow)]
pub struct DbVetcoveItem {
pub vetcove_item_id: i64,
pub item_name: String,
pub ignored: i64,
pub manufacturer_name: Option<String>,
pub manufacturer_number: Option<String>,
pub category: Option<String>,
pub units: Option<String>,
pub covetrus_sku: Option<String>,
pub mwi_sku: Option<String>,
pub patterson_sku: Option<String>,
pub midwest_sku: Option<String>,
pub first_vet_sku: Option<String>,
pub penn_vet_sku: Option<String>,
pub amatheon_sku: Option<String>,
pub victor_sku: Option<String>,
pub vetcove_sku: Option<String>,
pub miller_vet_sku: Option<String>,
pub boehringer_ingelheim_sku: Option<String>,
pub zoetis_sku: Option<String>,
pub pharmsource_ah_sku: Option<String>,
pub ne_animal_health_sku: Option<String>,
pub dechra_sku: Option<String>,
pub medline_sku: Option<String>,
pub elanco_sku: Option<String>,
}
pub async fn add_vetcove_item(db: &SqlitePool,
vetcove_item_id: i64,
item_name: String,
ignored: bool,
manufacturer_name: Option<String>,
manufacturer_number: Option<String>,
category: Option<String>,
units: Option<String>,
covetrus_sku: Option<String>,
mwi_sku: Option<String>,
patterson_sku: Option<String>,
midwest_sku: Option<String>,
first_vet_sku: Option<String>,
penn_vet_sku: Option<String>,
amatheon_sku: Option<String>,
victor_sku: Option<String>,
vetcove_sku: Option<String>,
miller_vet_sku: Option<String>,
boehringer_ingelheim_sku: Option<String>,
zoetis_sku: Option<String>,
pharmsource_ah_sku: Option<String>,
ne_animal_health_sku: Option<String>,
dechra_sku: Option<String>,
medline_sku: Option<String>,
elanco_sku: Option<String>)
-> Result<Option<i64>> {
let existing: i64 = sqlx::query_scalar(r#"
SELECT COUNT(1) FROM VetcoveItem WHERE vetcove_item_id = ?
"#).bind(vetcove_item_id).fetch_one(db).await?;
if existing > 0 {
return Ok(None)
}
let res = sqlx::query(
r#"
INSERT INTO VetcoveItem (
vetcove_item_id,
item_name,
ignored,
manufacturer_name,
manufacturer_number,
category,
units,
covetrus_sku,
mwi_sku,
patterson_sku,
midwest_sku,
first_vet_sku,
penn_vet_sku,
amatheon_sku,
victor_sku,
vetcove_sku,
miller_vet_sku,
boehringer_ingelheim_sku,
zoetis_sku,
pharmsource_ah_sku,
ne_animal_health_sku,
dechra_sku,
medline_sku,
elanco_sku)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"#)
.bind(vetcove_item_id)
.bind(item_name)
.bind(ignored)
.bind(manufacturer_name)
.bind(manufacturer_number)
.bind(category)
.bind(units)
.bind(covetrus_sku)
.bind(mwi_sku)
.bind(patterson_sku)
.bind(midwest_sku)
.bind(first_vet_sku)
.bind(penn_vet_sku)
.bind(amatheon_sku)
.bind(victor_sku)
.bind(vetcove_sku)
.bind(miller_vet_sku)
.bind(boehringer_ingelheim_sku)
.bind(zoetis_sku)
.bind(pharmsource_ah_sku)
.bind(ne_animal_health_sku)
.bind(dechra_sku)
.bind(medline_sku)
.bind(elanco_sku)
.execute(db).await?;
let _new_id = res.last_insert_rowid();
Ok(Some(vetcove_item_id))
}
pub async fn get_vetcove_item_by_id(db: &SqlitePool, vetcove_item_id: i64) -> Result<Option<DbVetcoveItem>> {
sqlx::query_as!(
DbVetcoveItem,
r#"
SELECT
vetcove_item_id,
item_name,
ignored,
manufacturer_name,
manufacturer_number,
category,
units,
covetrus_sku,
mwi_sku,
patterson_sku,
midwest_sku,
first_vet_sku,
penn_vet_sku,
amatheon_sku,
victor_sku,
vetcove_sku,
miller_vet_sku,
boehringer_ingelheim_sku,
zoetis_sku,
pharmsource_ah_sku,
ne_animal_health_sku,
dechra_sku,
medline_sku,
elanco_sku
FROM
VetcoveItem
WHERE VetcoveItem.vetcove_item_id = ?
"#,
vetcove_item_id
)
.fetch_optional(db).await
.map_err(From::from)
}
pub async fn get_vetcove_item_ignored(db: &SqlitePool, vetcove_item_id: i64)
-> Result<bool> {
let query_res: i64 = sqlx::query_scalar(r#"
SELECT ignored from VetcoveItem WHERE vetcove_item_id = ?
"#)
.bind(vetcove_item_id)
.fetch_one(db).await?;
let ignored = query_res != 0;
Ok(ignored)
}
pub async fn set_vetcove_item_ignored(db: &SqlitePool, vetcove_item_id: i64, ignored: bool)
-> Result<()> {
let affected = sqlx::query(r#"
UPDATE VetcoveItem SET ignored = ? WHERE vetcove_item_id = ?
"#)
.bind(ignored)
.bind(vetcove_item_id)
.execute(db).await?.rows_affected();
assert_eq!(affected, 1);
Ok(())
}
pub async fn add_vetcove_item_to_inventory_item_mapping(db: &SqlitePool,
vetcove_item_id: i64,
inventory_item_id: i64)
-> Result<i64> {
let res = sqlx::query(
r#"
INSERT INTO VetcoveItemToInventoryItemMap (
vetcove_item_id,
inventory_item_id
)
VALUES (?, ?)
"#)
.bind(vetcove_item_id)
.bind(inventory_item_id)
.execute(db).await?;
let new_id = res.last_insert_rowid();
Ok(new_id)
}
pub async fn map_vetcove_item_to_inventory_item(db: &SqlitePool,
vetcove_item_id: i64,
inventory_item_id: i64)
-> Result<()> {
let existing: i64 = sqlx::query_scalar(r#"
SELECT COUNT(1) FROM VetcoveItemToInventoryItemMap WHERE vetcove_item_id = ?
"#).bind(vetcove_item_id).fetch_one(db).await?;
if existing > 0 {
let res = sqlx::query(r#"
UPDATE VetcoveItemToInventoryItemMap
SET inventory_item_id = ?
WHERE vetcove_item_id = ?
"#)
.bind(inventory_item_id)
.bind(vetcove_item_id)
.execute(db).await?.rows_affected();
assert_eq!(res, 1);
}
else {
let res = sqlx::query(r#"
INSERT INTO VetcoveItemToInventoryItemMap (vetcove_item_id, inventory_item_id)
VALUES (?, ?)
"#)
.bind(vetcove_item_id)
.bind(inventory_item_id)
.execute(db).await?.rows_affected();
assert_eq!(res, 1);
}
Ok(())
}
pub async fn get_inventory_item_id_by_vetcove_item_id(db: &SqlitePool, vetcove_item_id: i64)
-> Result<Option<i64>> {
let value: Option<i64> = sqlx::query_scalar(r#"
SELECT inventory_item_id FROM VetcoveItemToInventoryItemMap WHERE vetcove_item_id = ?
"#).bind(vetcove_item_id).fetch_optional(db).await?;
Ok(value)
}
pub async fn get_vetcove_item_ids_by_inventory_item_id(db: &SqlitePool, inventory_item_id: i64)
-> Result<Vec<i64>> {
let value: Vec<i64> = sqlx::query_scalar(r#"
SELECT inventory_item_id FROM VetcoveItemToInventoryItemMap WHERE vetcove_item_id = ?
"#).bind(inventory_item_id).fetch_all(db).await?;
Ok(value)
}

@ -34,7 +34,7 @@ pub struct DbVetcoveItemHistoryEntry {
pub async fn add_vetcove_item_history_entry(db: &SqlitePool,
vetcove_entry_id: i64,
vetcove_history_entry_id: i64,
vetcove_item_id: i64,
order_date: NaiveDate,
item_name: String,
@ -58,8 +58,8 @@ pub async fn add_vetcove_item_history_entry(db: &SqlitePool,
-> Result<Option<i64>> {
let existing: i64 = sqlx::query_scalar(r#"
SELECT COUNT(1) FROM VetcoveItemHistory WHERE vetcove_entry_id = ?
"#).bind(vetcove_entry_id).fetch_one(db).await?;
SELECT COUNT(1) FROM VetcoveItemHistory WHERE vetcove_history_entry_id = ?
"#).bind(vetcove_history_entry_id).fetch_one(db).await?;
if existing > 0 {
return Ok(None)
@ -68,7 +68,7 @@ pub async fn add_vetcove_item_history_entry(db: &SqlitePool,
let res = sqlx::query(
r#"
INSERT INTO VetcoveItemHistory (
vetcove_entry_id,
vetcove_history_entry_id,
vetcove_item_id,
order_date,
item_name,
@ -91,7 +91,7 @@ pub async fn add_vetcove_item_history_entry(db: &SqlitePool,
supplier_sku )
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"#)
.bind(vetcove_entry_id)
.bind(vetcove_history_entry_id)
.bind(vetcove_item_id)
.bind(order_date)
.bind(item_name)
@ -114,7 +114,28 @@ pub async fn add_vetcove_item_history_entry(db: &SqlitePool,
.bind(supplier_sku)
.execute(db).await?;
let _new_id = res.last_insert_rowid();
Ok(Some(vetcove_history_entry_id))
}
pub async fn add_item_history_to_adjustment_mapping(db: &SqlitePool,
vetcove_history_entry_id: i64,
adjustment_id: i64)
-> Result<i64> {
let res = sqlx::query(
r#"
INSERT INTO VetcoveItemHistoryToAdjustmentMap (
vetcove_history_entry_id,
adjustment_id
)
VALUES (?, ?)
"#)
.bind(vetcove_history_entry_id)
.bind(adjustment_id)
.execute(db).await?;
let new_id = res.last_insert_rowid();
Ok(Some(new_id))
Ok(new_id)
}

@ -1 +1,2 @@
pub mod item_history;
pub mod item_history;
pub mod item;

@ -15,6 +15,8 @@ struct CatalogRecord {
reorder_point: f64,
#[serde(alias = "price")]
unit_price: i64,
#[serde(alias = "vetcove id")]
vetcove_id: String,
}
pub async fn ingest_catalog_bytes(bytes: Bytes, db: SqlitePool, user_id: i64) -> anyhow::Result<()> {
@ -36,7 +38,6 @@ pub async fn ingest_catalog<T: std::io::Read>(mut reader: csv::Reader<T>, db: Sq
record.fractional,
&record.unit,
&None,
&None,
).await?;
let new_positive_adjustment = add_adjustment_new_stock(&db, new_entry_id,

@ -1,69 +1,220 @@
use anyhow::anyhow;
use sqlx::SqlitePool;
use tracing::info;
use tracing::{error, info};
use axum::body::Bytes;
use crate::db::adjustment::add_adjustment_new_stock;
use crate::db::inventory_item::add_inventory_item;
use crate::db::vetcove::item_history::add_vetcove_item_history_entry;
use crate::db::adjustment::{add_adjustment, add_adjustment_new_stock};
use crate::db::adjustment::adjustment_reason::DbAdjustmentReason;
use crate::db::vetcove::item_history::{add_item_history_to_adjustment_mapping, add_vetcove_item_history_entry};
use crate::db::vetcove::item::{add_vetcove_item, get_inventory_item_id_by_vetcove_item_id, get_vetcove_item_by_id, get_vetcove_item_ignored};
use crate::util::currency::dollars_string_to_int_cents;
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct VetcoveItemRecord {
#[serde(alias = "Vetcove ID")]
pub vetcove_item_id: i64,
#[serde(alias = "Item Name")]
pub item_name: String,
#[serde(alias = "Manufacturer Name")]
pub manufacturer_name: String,
#[serde(alias = "Manufacturer No.")]
pub manufacturer_number: String,
#[serde(alias = "Category")]
pub category: String,
#[serde(alias = "Units")]
pub units: String,
#[serde(alias = "Last Purchase")]
pub last_purchase: String,
#[serde(alias = "Last Purchase List Price")]
pub last_purchase_list_price: String,
#[serde(alias = "Number of Orders")]
pub number_of_orders: String,
#[serde(alias = "Percentage of Total Volume")]
pub percentage_of_total_volume: String,
#[serde(alias = "Quantity")]
pub quantity: String,
#[serde(alias = "Total")]
pub total: String,
#[serde(alias = "Covetrus SKU")]
pub covetrus_sku: String,
#[serde(alias = "MWI SKU")]
pub mwi_sku: String,
#[serde(alias = "Patterson SKU")]
pub patterson_sku: String,
#[serde(alias = "Midwest SKU")]
pub midwest_sku: String,
#[serde(alias = "First Vet SKU")]
pub first_vet_sku: String,
#[serde(alias = "PennVet SKU")]
pub pennvet_sku: String,
#[serde(alias = "Amatheon SKU")]
pub amatheon_sku: String,
#[serde(alias = "Victor SKU")]
pub victor_sku: String,
#[serde(alias = "Vetcove SKU")]
pub vetcove_sku: String,
#[serde(alias = "Miller Vet SKU")]
pub miller_vet_sku: String,
#[serde(alias = "Boehringer Ingelheim SKU")]
pub boehringer_ingelheim_sku: String,
#[serde(alias = "Zoetis SKU")]
pub zoetis_sku: String,
#[serde(alias = "Pharmsource AH SKU")]
pub pharmsource_ah_sku: String,
#[serde(alias = "NE Animal Health SKU")]
pub ne_animal_health_sku: String,
#[serde(alias = "Dechra SKU")]
pub dechra_sku: String,
#[serde(alias = "Medline SKU")]
pub medline_sku: String,
#[serde(alias = "Elanco SKU")]
pub elanco_sku: String,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct VetcoveItemImportRowResult {
pub already_imported: bool,
pub item: VetcoveItemRecord,
pub item_is_ignored: bool,
}
pub async fn ingest_vetcove_items_bytes(bytes: Bytes, db: SqlitePool, user_id: i64) -> anyhow::Result<Vec<VetcoveItemImportRowResult>> {
let reader = csv::Reader::from_reader(bytes.as_ref());
ingest_vetcove_items(reader, db, user_id).await
}
pub async fn ingest_vetcove_items<T: std::io::Read>(mut reader: csv::Reader<T>, db: SqlitePool, user_id: i64) -> anyhow::Result<Vec<VetcoveItemImportRowResult>>
{
let timestamp = chrono::Utc::now();
let mut import_results = vec![];
for result in reader.deserialize() {
let record: VetcoveItemRecord = result?;
let vetcove_item_id = record.vetcove_item_id;
let mut row_result = VetcoveItemImportRowResult {
already_imported: false, item: record.clone(), item_is_ignored: false
};
let query_res = add_vetcove_item(
&db,
record.vetcove_item_id,
record.item_name,
false,
Some(record.manufacturer_name),
Some(record.manufacturer_number),
Some(record.category),
Some(record.units),
Some(record.covetrus_sku),
Some(record.mwi_sku),
Some(record.patterson_sku),
Some(record.midwest_sku), Some(record.first_vet_sku), Some(record.pennvet_sku),
Some(record.amatheon_sku), Some(record.victor_sku), Some(record.vetcove_sku),
Some(record.miller_vet_sku), Some(record.boehringer_ingelheim_sku),
Some(record.zoetis_sku), Some(record.pharmsource_ah_sku),
Some(record.ne_animal_health_sku), Some(record.dechra_sku), Some(record.medline_sku),
Some(record.elanco_sku),
).await?;
if query_res.is_none() {
info!("Vetcove item already imported, skipping {}", vetcove_item_id);
row_result.already_imported = true;
}
//NOTE: Might want to grab more info from the db at some point, but for now anything other
// than 'ignored' is duplicate info
let ignored = get_vetcove_item_ignored(&db, vetcove_item_id).await?;
row_result.item_is_ignored = ignored;
import_results.push(row_result);
}
Ok(import_results)
}
#[derive(Debug, serde::Deserialize)]
struct ItemHistoryRecord {
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct VetcoveItemHistoryRecord {
#[serde(alias = "Vetcove ID")]
vetcove_entry_id: i64,
pub vetcove_entry_id: i64,
#[serde(alias = "Date")]
order_date: chrono::NaiveDate,
pub order_date: chrono::NaiveDate,
#[serde(alias = "Hospital")]
hospital: String,
pub hospital: String,
#[serde(alias = "Name")]
item_name: String,
pub item_name: String,
#[serde(alias = "Order #")]
order_id: String,
pub order_id: String,
#[serde(alias = "PO Number")]
po_number: String,
pub po_number: String,
#[serde(alias = "Supplier")]
supplier: String,
pub supplier: String,
#[serde(alias = "Manufacturer")]
manufacturer: String,
pub manufacturer: String,
#[serde(alias = "Manufacturer Number")]
manufacturer_number: String,
pub manufacturer_number: String,
#[serde(alias = "Primary Category")]
primary_category: String,
pub primary_category: String,
#[serde(alias = "Secondary Category")]
secondary_category: String,
pub secondary_category: String,
#[serde(alias = "Vetcove Item ID")]
vetcove_item_id: i64,
pub vetcove_item_id: i64,
#[serde(alias = "Cost per Dose")]
cost_per_dose: String,
pub cost_per_dose: String,
#[serde(alias = "Units")]
units: f64,
pub units: f64,
#[serde(alias = "Unit Price")]
unit_price: String,
pub unit_price: String,
#[serde(alias = "Unit Measurement")]
unit_measurement: String,
pub unit_measurement: String,
#[serde(alias = "List Price")]
list_price: String,
pub list_price: String,
#[serde(alias = "Quantity")]
quantity: f64,
pub quantity: f64,
#[serde(alias = "Total Price")]
total_price: String,
pub total_price: String,
#[serde(alias = "Item Status")]
item_status: String,
pub item_status: String,
#[serde(alias = "Supplier's SKU")]
supplier_sku: String,
pub supplier_sku: String,
}
pub async fn ingest_item_history_bytes(bytes: Bytes, db: SqlitePool, user_id: i64) -> anyhow::Result<()> {
#[derive(Debug, Clone, Copy, serde::Serialize)]
pub enum ItemHistoryImportRowErrorKind {
AlreadyImported,
VetcoveIdNotFound,
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ItemHistoryImportRowResult {
pub error: Option<ItemHistoryImportRowErrorKind>,
pub item: VetcoveItemHistoryRecord,
}
pub async fn ingest_item_history_bytes(bytes: Bytes, db: SqlitePool, user_id: i64) -> anyhow::Result<Vec<ItemHistoryImportRowResult>> {
let reader = csv::Reader::from_reader(bytes.as_ref());
ingest_item_history(reader, db, user_id).await
}
pub async fn ingest_item_history<T: std::io::Read>(mut reader: csv::Reader<T>, db: SqlitePool, user_id: i64) -> anyhow::Result<()>
pub async fn ingest_item_history<T: std::io::Read>(mut reader: csv::Reader<T>, db: SqlitePool, user_id: i64) -> anyhow::Result<Vec<ItemHistoryImportRowResult>>
{
let timestamp = chrono::Utc::now();
let mut import_results = vec![];
for result in reader.deserialize() {
let record: ItemHistoryRecord = result?;
let record: VetcoveItemHistoryRecord = result?;
let mut row_result = ItemHistoryImportRowResult { error: None, item: record.clone() };
let vetcove_item = get_vetcove_item_by_id(&db, record.vetcove_item_id).await?;
if vetcove_item.is_none() {
info!("Unable to add entry, no matching vetcove ID {}", record.vetcove_entry_id);
row_result.error = Some(ItemHistoryImportRowErrorKind::VetcoveIdNotFound);
import_results.push(row_result);
continue;
}
let query_res = add_vetcove_item_history_entry(
&db,
@ -90,10 +241,13 @@ pub async fn ingest_item_history<T: std::io::Read>(mut reader: csv::Reader<T>, d
Some(record.supplier_sku),
).await?;
match query_res {
Some(new_db_id) => info!("Added new history item: {} => {}", record.vetcove_entry_id, new_db_id),
None => info!("History item exists, skipping {}", record.vetcove_entry_id),
if query_res.is_none() {
info!("History item exists, skipping {}", record.vetcove_entry_id);
row_result.error = Some(ItemHistoryImportRowErrorKind::AlreadyImported);
}
import_results.push(row_result);
}
Ok(())
Ok(import_results)
}

@ -108,26 +108,6 @@
{% endif -%}
</div>
<div class="col-span-3">
<label for="vetcove_id" class="mb-2 block text-sm font-medium">Vetcove Id</label>
<input
type="text"
id="vetcove_id"
name="vetcove_id"
class="block p-2 rounded-lg border border-neutral-900 text-sm text-neutral-900 focus:border-paynes-gray focus:ring-paynes-gray dark:text-slate-100"
value="{{ vetcove_id.value }}"
aria-label="vetcove id"
{% if vetcove_id.is_error() -%}
aria-invalid="true"
aria-describedby="invalid-vetcove-id"
{% endif -%}
/>
{% if vetcove_id.is_error() -%}
<small id="invalid-vetcove-id" class="block mt-2 text-sm text-cerise">
{{ vetcove_id.error_string() }}
</small>
{% endif -%}
</div>
<div class="col-span-4">
<button
class="mb-2 me-2 rounded-lg bg-english-violet px-5 py-2.5 text-sm font-medium text-slate-100 hover:bg-dark-cyan focus:outline-none focus:ring-4 focus:ring-dark-cyan"

@ -108,26 +108,6 @@
{% endif -%}
</div>
<div class="col-span-3">
<label for="vetcove_id" class="mb-2 block text-sm font-medium">Vetcove Id</label>
<input
type="text"
id="vetcove_id"
name="vetcove_id"
class="block p-2 rounded-lg border border-neutral-900 text-sm text-neutral-900 focus:border-paynes-gray focus:ring-paynes-gray dark:text-slate-100"
value="{{ vetcove_id.value }}"
aria-label="vetcove id"
{% if vetcove_id.is_error() -%}
aria-invalid="true"
aria-describedby="invalid-vetcove-id"
{% endif -%}
/>
{% if vetcove_id.is_error() -%}
<small id="invalid-vetcove-id" class="block mt-2 text-sm text-cerise">
{{ vetcove_id.error_string() }}
</small>
{% endif -%}
</div>
<div class="col-span-4">
<button
class="mb-2 me-2 rounded-lg bg-english-violet px-5 py-2.5 text-sm font-medium text-slate-100 hover:bg-dark-cyan focus:outline-none focus:ring-4 focus:ring-dark-cyan"

@ -1,52 +0,0 @@
{% extends "main.html" %} {% block title %} Upload {% endblock %} {% block
content %}
<div class="relative h-auto mx-auto max-w-[68.75rem] w-[95vw] px-4 py-4">
<form
action="/upload/catalog"
method="post"
enctype="multipart/form-data"
x-data="{ file: '' }"
>
<fieldset class="grid">
<h3>Catalog Import</h3>
<label role="button" class="secondary" x-show="!file">
Choose File
<input
type="file"
name="file"
x-model="file"
style="display: none"
required
/>
</label>
<input type="submit" value="Upload" x-show="file" />
<input type="reset" value="Cancel" x-show="file" />
</fieldset>
</form>
<form
action="/upload/vetcove/history"
method="post"
enctype="multipart/form-data"
x-data="{ file: '' }"
>
<fieldset class="grid">
<h3>Vetcove Item History</h3>
<label role="button" class="secondary" x-show="!file">
Choose File
<input
type="file"
name="file"
x-model="file"
style="display: none"
required
/>
</label>
<input type="submit" value="Upload" x-show="file" />
<input type="reset" value="Cancel" x-show="file" />
</fieldset>
</form>
</div>
{% endblock %}

@ -0,0 +1,91 @@
{% extends "main.html" %} {% block title %} Upload {% endblock %} {% block
content %}
<div class="relative h-auto mx-auto max-w-[68.75rem] w-[95vw] px-4 py-4">
<form
action="/upload/catalog"
method="post"
enctype="multipart/form-data"
x-data="{ file: '' }"
>
<fieldset class="grid">
<h3>Catalog Import</h3>
<label role="button" class="secondary" x-show="!file">
Choose File
<input
type="file"
name="file"
x-model="file"
style="display: none"
required
/>
</label>
<input type="submit" value="Upload" x-show="file" />
<input type="reset" value="Cancel" x-show="file" />
</fieldset>
</form>
<form
id="vetcove-history-upload"
enctype="multipart/form-data"
hx-post="/upload/vetcove/history"
hx-target="#upload-results"
x-data="{ file: '' }"
x-on:htmx:response-error="$dispatch('notice', {type: 'error', text: 'Unknown error'})"
>
<h3>Vetcove Item History</h3>
<label role="button"
class="mb-2 me-2 rounded-lg bg-english-violet px-5 py-2.5 text-sm font-medium text-slate-100 hover:bg-dark-cyan focus:outline-none focus:ring-4 focus:ring-dark-cyan"
x-show="!file">
Choose File
<input
type="file"
name="file"
x-model="file"
style="display: none"
required
/>
</label>
<input type="submit" value="Upload"
class="mb-2 me-2 rounded-lg bg-english-violet px-5 py-2.5 text-sm font-medium text-slate-100 hover:bg-dark-cyan focus:outline-none focus:ring-4 focus:ring-dark-cyan"
x-show="file" />
<input type="reset" value="Cancel"
class="mb-2 me-2 rounded-lg bg-english-violet px-5 py-2.5 text-sm font-medium text-slate-100 hover:bg-dark-cyan focus:outline-none focus:ring-4 focus:ring-dark-cyan"
x-show="file" />
</form>
<form
id="vetcove-items-upload"
enctype="multipart/form-data"
hx-post="/upload/vetcove/items"
hx-target="#upload-results"
x-data="{ file: '' }"
x-on:htmx:response-error="$dispatch('notice', {type: 'error', text: 'Unknown error'})"
>
<h3>Vetcove Items</h3>
<label role="button"
class="mb-2 me-2 rounded-lg bg-english-violet px-5 py-2.5 text-sm font-medium text-slate-100 hover:bg-dark-cyan focus:outline-none focus:ring-4 focus:ring-dark-cyan"
x-show="!file">
Choose File
<input
type="file"
name="file"
x-model="file"
style="display: none"
required
/>
</label>
<input type="submit" value="Upload"
class="mb-2 me-2 rounded-lg bg-english-violet px-5 py-2.5 text-sm font-medium text-slate-100 hover:bg-dark-cyan focus:outline-none focus:ring-4 focus:ring-dark-cyan"
x-show="file" />
<input type="reset" value="Cancel"
class="mb-2 me-2 rounded-lg bg-english-violet px-5 py-2.5 text-sm font-medium text-slate-100 hover:bg-dark-cyan focus:outline-none focus:ring-4 focus:ring-dark-cyan"
x-show="file" />
</form>
<div id="upload-results"></div>
</div>
{% endblock %}

@ -0,0 +1,39 @@
<table class="w-full text-left text-sm rtl:text-right">
<thead class="bg-gray-50 text-xs uppercase text-gray-700 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th class="px-6 py-3" scope="col">Status</th>
<th class="px-6 py-3" scope="col">Vetcove ID</th>
<th class="px-6 py-3" scope="col">Name</th>
</tr>
</thead>
<tbody>
{% for res in results %}
{% match res.error %}
{% when Some(ItemHistoryImportRowErrorKind::AlreadyImported) %}
<tr class="text-slate-500">
<td class="px-6 py-4"> Skip </td>
<td class="px-6 py-4">{{ res.item.vetcove_item_id }}</td>
<td class="px-6 py-4">{{ res.item.item_name }}</td>
</tr>
{% when Some(ItemHistoryImportRowErrorKind::VetcoveIdNotFound) %}
<tr class="text-cerise">
<td class="px-6 py-4"> Id Not Found </td>
<td class="px-6 py-4">{{ res.item.vetcove_item_id }}</td>
<td class="px-6 py-4">{{ res.item.item_name }}</td>
</tr>
{% when None %}
<tr class="text-green-700">
<td class="px-6 py-4"> Success </td>
<td class="px-6 py-4">{{ res.item.vetcove_item_id }}</td>
<td class="px-6 py-4">{{ res.item.item_name }}</td>
</tr>
{% else %}
<tr class="text-cerise">
<td class="px-6 py-4"> Unknown Error </td>
<td class="px-6 py-4">{{ res.item.vetcove_item_id }}</td>
<td class="px-6 py-4">{{ res.item.item_name }}</td>
</tr>
{% endmatch %}
{% endfor %}
</tbody>
</table>

@ -0,0 +1,26 @@
<table class="w-full text-left text-sm rtl:text-right">
<thead class="bg-gray-50 text-xs uppercase text-gray-700 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th class="px-6 py-3" scope="col">Status</th>
<th class="px-6 py-3" scope="col">Vetcove ID</th>
<th class="px-6 py-3" scope="col">Name</th>
</tr>
</thead>
<tbody>
{% for res in results %}
{% if res.already_imported %}
<tr class="text-slate-500">
<td class="px-6 py-4"> Skip </td>
<td class="px-6 py-4">{{ res.item.vetcove_item_id }}</td>
<td class="px-6 py-4">{{ res.item.item_name }}</td>
</tr>
{% else %}
<tr class="text-green-700">
<td class="px-6 py-4"> Imported </td>
<td class="px-6 py-4">{{ res.item.vetcove_item_id }}</td>
<td class="px-6 py-4">{{ res.item.item_name }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
Loading…
Cancel
Save

Powered by TurnKey Linux.