Евгений Колесников 4 лет назад
Родитель
Сommit
5fe92c3141

+ 62 - 0
articles/wsr_1.md

@@ -0,0 +1,62 @@
+# Сессия 1. Создание БД. Импорт данных. Окно авторизации
+
+>Ресурсы к первой сессии находятся в файле `data/Sessio1.zip` этого репозитория
+
+## База данных и импорт
+
+Создайте базу данных, используя предпочтительную платформу (MySQL / Microsoft SQL Server), на сервере баз данных, который вам предоставлен.
+
+Создайте таблицы основных сущностей, атрибуты, отношения и необходимые ограничения. После создания базы данных требуется импортировать предоставленные данные (см. ресурсы к первой сессии). Возможно, вам понадобится отформатировать данные, прежде чем загрузить их в таблицы, которые вы только что создали. В любом случае созданные таблицы должны содержать начальные тестовые данные.
+
+Порядок работы лаборатории: на каждую единицу принятого биоматериала создается заказ, который может содержать в себе услуги (одну или несколько) – исследования биоматериала. У одного пациента может быть несколько заказов. Хранение данных о всех пациентах и заказах позволит формировать все необходимые отчеты, отслеживать динамику показателей и состояние здоровья пациента, а так же автоматизировать работу сотрудников лаборатории. 
+
+Обеспечьте хранение в базе данных:
+
+* услуги лаборатории (наименование, стоимость, код услуги, срок выполнения, среднее отклонение)
+* данные пациентов (логин, пароль, ФИО, дата рождения, серия и номер паспорта, телефон, e-mail, номер страхового полиса, тип страхового полиса, страховая компания)
+* данные о страховых компаниях (название страховой компании, адрес, ИНН, р/с, БИК)
+* заказ (дата создания, которые входят в заказ, услуги,  статус заказа, статус услуги в заказе, время выполнения заказа (в днях))
+* оказанная услуга (услуга, когда и кем была и на каком анализаторе)
+* данные о работе анализатора (дата и время поступления заказа на анализатор, дата и время выполнения (в секундах) услуг на анализаторе)
+* данные лаборантов (логин, пароль, ФИО, последняя дата и время входа, набор услуг, которые он может оказывать)
+* бухгалтер  (логин, пароль, ФИО, последняя дата и время входа, набор услуг, выставленные счета страховым компаниям)
+* администратор (логин и пароль)
+
+При организации хранения данных вам необходимо учесть запрет на полное удаление данных, реализовав возможность отправки данных в архив. Кроме того, необходимо учесть, что данные о заказе не могут быть отправлены в архив, если не выполнена хотя-бы одна услуга в заказе. 
+
+Разработанная вами база данных должна быть в 3 НФ.
+
+К разработанной баз данных создайте словарь данных (пример словаря данных в папке с ресурсами).
+По окончании сессии разработанная вами база данных будет оценена экспертной группой. В последующих сессиях возможно вам понадобится добавить какие-либо сущности в ходе работы над проектом. 
+
+## Окно входа
+
+При запуске приложения окно входа – первое, что видит пользователь. На ней пользователю предлагается ввести свой логин и пароль. Только после удачной авторизации пользователь получает доступ к остальным модулям системы.
+
+При вводе пароль должен быть скрыт маской ввода, но так же должна быть реализована возможность просмотра введенного пароля. 
+
+При входе система выводит фото пользователя, фамилию и имя пользователя, его роль.
+
+После авторизации пользователь получает доступ к нужному функционалу: 
+
+* лаборант может принять биоматериал, сформировать отчеты;
+* лаборант-исследователь может работать с анализатором;
+* бухгалтер может просмотреть отчеты, сформировать счет страховой компании;
+* администратор может сформировать отчеты,  проконтролировать всех пользователей по истории входа, работать с данными о расходных материалах, используемых в лаборатории. 
+
+Реализуйте необходимые интерфейсы для всех пользователей системы. После входа в любую учетную запись должна быть реализована возможность выхода на главный экран – окно входа. 
+
+При входе в учетную запись лаборанта и лаборанта-исследователя должен быть виден таймер (часы:минуты), который фиксирует время сеанса пользователя. Сеанс пользователя не должен превышать 2 ч 30 минут, так как через каждые 2 ч 30 минут необходимо выполнить кварцевание помещений. За 15 минут до окончания времени сеанса должно появиться сообщение об окончании времени сеанса. По окончании времени сеанса реализуйте выход из учетной записи и блокировку входа на 30 минут.
+
+Для удобства проверки экспертной группой  - укажите время сеанса – 10 минут, появление сообщения – за 5 минут до окончания времени сеанса, блокировка входа – 1 минута. 
+
+После первой попытки неуспешной авторизации система выдает сообщение о неуспешной авторизации, а затем помимо ввода логина и пароля просит ввести **captcha**, состоящую из 4 символов (цифры и буквы латинского алфавита) и графического шума. 
+
+CAPTCHA - должна содержать минимум 4 символа (буква или цифра), которые выведены не в одной линии. Символы должны быть либо перечеркнуты либо наложены друг на друга.
+
+Реализуйте возможность повторной генерации **captcha**, если пользователю непонятны символы из-за шума. 
+После попытки неудачной авторизации с вводом **captcha**, система блокирует возможность входа на 10 секунд. 
+
+## История входа
+
+Приложение должно хранить историю входа в систему, так как в системе будут храниться медицинские данные пациентов. Окно для просмотра истории должно быть доступно администратору системы. В этом окне необходимо реализовать просмотр всей истории входа, а также фильтрацию по логину пользователя. Кроме этого, необходимо добавить сортировку по дате попытки входа. Каждая запись истории должна содержать следующие данные: время, логин пользователя, успешная или ошибочная попытка входа.

BIN
data/Session1.zip


+ 51 - 2
shpora/HttpHelper.kt

@@ -1,5 +1,3 @@
-package ru.yotc.myapplication
-
 import android.graphics.Bitmap
 import android.graphics.BitmapFactory
 import org.json.JSONObject
@@ -17,6 +15,57 @@ import javax.net.ssl.HttpsURLConnection
 
 И атрибут в тег application
 android:usesCleartextTraffic="true"
+
+Использование:
+
+HTTP.requestGET(
+    "http://s4a.kolei.ru/Product",
+    mapOf(
+        "token" to token
+    )
+){result, error ->
+    runOnUiThread{
+        if(result!=null){
+            resultTextView.text = result
+        }
+        else
+            AlertDialog.Builder(this)
+                .setTitle("Ошибка http-запроса")
+                .setMessage(error)
+                .setPositiveButton("OK", null)
+                .create()
+                .show()
+    }
+}
+
+HTTP.requestPOST(
+    "http://s4a.kolei.ru/login",
+    JSONObject().put("username", username).put("password", password),
+    mapOf(
+        "Content-Type" to "application/json"
+    )
+){result, error ->
+    runOnUiThread{
+        if(result!=null){
+        }
+        else
+            AlertDialog.Builder(this)
+                .setTitle("Ошибка http-запроса")
+                .setMessage(error)
+                .setPositiveButton("OK", null)
+                .create()
+                .show()
+    }
+}
+
+HTTP.getImage("https://openweathermap.org/img/w/${icoName}.png") { bitmap, error ->
+    runOnUiThread {
+        if (bitmap != null) {
+            var imageView = findViewById<ImageView>(R.id.ico)
+            imageView.setImageBitmap(bitmap)
+        }
+    }
+}
 */
 
 object HTTP

+ 0 - 2
shpora/Locator.kt

@@ -1,5 +1,3 @@
-package ru.yotc.myapplication
-
 import android.Manifest
 import android.annotation.SuppressLint
 import android.content.pm.PackageManager

+ 7 - 2
shpora/LoginDialog.kt

@@ -1,5 +1,3 @@
-package ru.yotc.demoex
-
 import android.app.Dialog
 import android.content.DialogInterface
 import android.os.Bundle
@@ -8,6 +6,13 @@ import android.widget.TextView
 import androidx.appcompat.app.AlertDialog
 import androidx.fragment.app.DialogFragment
 
+/*
+Использование:
+LoginDialog { login, password ->
+    // тут реализация 
+}.show(supportFragmentManager, null)
+*/
+
 class LoginDialog(private val callback: (login: String, password: String)->Unit) : DialogFragment() {
 
     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

+ 43 - 10
shpora/WeatherAdapter.kt

@@ -1,5 +1,3 @@
-package ru.yotc.myapplication
-
 import android.app.Activity
 import android.content.Context
 import android.util.Log
@@ -13,14 +11,49 @@ import androidx.recyclerview.widget.RecyclerView
 import java.lang.Exception
 import java.util.ArrayList
 
-/**
- * Класс адаптера наследуется от RecyclerView.Adapter с указанием класса,
- * который будет хранить ссылки на виджеты элемента списка, т.е. класса, реализующего ViewHolder.
- * В нашем случае класс объявлен внутри класса адаптера.
- *
- * В параметры основного конструктора передаем список c данными о погоде и указатель на активити главного окна
- * дело в том, что runOnUiThread работает только в контексте активити
- */
+/*
+Класс адаптера наследуется от RecyclerView.Adapter с указанием класса,
+который будет хранить ссылки на виджеты элемента списка, т.е. класса, реализующего ViewHolder.
+В нашем случае класс объявлен внутри класса адаптера.
+
+В параметры основного конструктора передаем список c данными о погоде и указатель на активити главного окна
+дело в том, что runOnUiThread работает только в контексте активити
+
+Использование:
+
+в КЛАССЕ активности объявляем переменные
+private lateinit var someRecyclerView: RecyclerView
+private val someClassList = ArrayList<SomeClass>()
+
+в КОНСТРУКТОРЕ инициализируем:
+
+someRecyclerView = findViewById(R.id.someRecyclerView)
+
+// назначаем менеджер разметки
+someRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)
+
+// создаем адаптер
+val someClassAdapter = WeatherAdapter(someClassList, this)
+
+// при клике на элемент списка показать подробную информацию (сделайте сами)
+someClassAdapter.setItemClickListener { weather ->
+    Log.d("KEILOG", "Click on Weather item")
+}
+
+someRecyclerView.adapter = weatherAdapter
+
+разбор JSONObject
+// перед заполнением очищаем список
+someClassList.clear()
+
+val json = JSONObject(result)
+val list = json.getJSONArray("list")
+
+// перебираем json массив
+for(i in 0 until list.length()){
+    val item = list.getJSONObject(i)
+    ...
+*/
 class WeatherAdapter(
     private val values: ArrayList<Weather>,
     private val activity: Activity

+ 20 - 0
shpora/gradient.md

@@ -0,0 +1,20 @@
+Рисует градиент слева направо от **startColor** до **endColor** с возможностью перехода через **centerColor**. Для смены направления использовать **angle**
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:startColor="color"
+        android:endColor="color"
+        android:centerColor="integer"
+        android:angle="integer"
+
+        android:centerX="float"
+        android:centerY="float"
+        android:gradientRadius="integer"
+        android:type=["linear" | "radial" | "sweep"]
+        android:useLevel=["true" | "false"] />
+</shape>
+```

+ 15 - 0
shpora/round_border.md

@@ -0,0 +1,15 @@
+Создаём *drawable* ресурс с типом *shape*
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners
+        android:radius="40dp">
+    </corners>
+    <solid
+        android:color="#00FF00">
+    </solid>
+</shape>
+```

+ 46 - 0
shpora/startActivity.md

@@ -0,0 +1,46 @@
+Запуск activity
+
+```kt
+val mainIntent = Intent(this, MainActivity::class.java)
+
+// если надо что-то передать в активность
+mainIntent.putExtra("city_name", cityName)
+
+// запуск активности
+startActivity( mainIntent )
+
+// возврат из запущенной активности
+finish()
+
+// получение переданных параметров (intent создавать не надо, он есть у activity)
+intent.getStringExtra("city_name")
+```
+
+Если надо получить результат
+
+```kt
+// запуск
+startActivityForResult( 
+    Intent(this, CityListActivity::class.java), 
+    1
+)
+
+// получение результата
+// здесь requestCode = 1 (ответы от всех запущенных activity приходят в одно место)
+// resultCode задается при ответе
+// data необязательные данные из newIntent
+@SuppressLint("MissingSuperCall")
+override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+    if (data == null) {
+        return
+    }
+    val name = data.getStringExtra("cityName")
+}
+
+// в запущенном перед закрытием установить результат
+// запоминаем выбранное название города в параметрах
+val newIntent = Intent()
+newIntent.putExtra("cityName", cityName)
+setResult(RESULT_OK, newIntent)
+finish()
+```