parent
801d37202e
commit
846d8d5dde
|
@ -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)
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
pub mod item_history;
|
pub mod item_history;
|
||||||
|
pub mod item;
|
||||||
@ -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…
Reference in new issue