Рапорт руководителю

Проектирование Android – приложения

Созданный в 1С шаблон Android-проекта импортируем в рабочее пространство Eclipse.

Запустите Eclipse и выполните импорт проекта: File > Import… В группе Android выберите элемент «Exists Android Code Into Workspace»,  нажмите Next, укажите путь к каталогу шаблона Android-проекта и нажмите Finish, новый проект будет добавлен в рабочее пространство. Если возникнут сложности см. здесь, процесс описан в картинках.

1. Создаем отчет «Финансовые показатели», источником данных для которого является таблица значений, полученная от 1С. Для этого добавьте новый класс ToChiefReport.java, вот полный исходный код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
 * Пример построения отчета по данным из локальной базы sqlite. Источником
 * данных выступает внешняя таблица «Рапорт руководителю»
 *
 */
public class ToChiefReport extends SimpleMapHtmlReport {
 
    @Override
    public int getResIdIcon() {
            return R.drawable.report_01;
    }
 
    @Override
    public int getResIdTitle() {
            return R.string.report_name_to_chief;
    }
 
    @Override
    public void build(Context context, IReportBuilderResult builderResult) {
 
            try {
                    makeReport(context);
            } catch (SQLException e) {
                    e.printStackTrace();
                    setHeader1("Упс, отчет не создан!");
            }
 
            super.build(context, builderResult);
    }
 
    private void makeReport(Context context) throws SQLException {
 
            DBHelper helper = new DBHelper(context);
 
            // Заголовок отчета и таблицы
            setHeader2("Финансовые показатели");
            setTableHeader("Наименование ", "Сумма, руб.");
 
            // Строки таблицы будут в порядке добавления
            Map<Object, Object> mapData = new LinkedHashMap<Object, Object>();
 
            // Выборка данных из локальной базы данных sqlite, внешняя таблица
            // «Рапорт руководителю»
            ExTableRaportRukovoditelyuDao dao = helper
                            .getDao(ExTableRaportRukovoditelyu.class);
            List<ExTableRaportRukovoditelyu> rows = dao.select();
            for (ExTableRaportRukovoditelyu row : rows) {
                    mapData.put(row.pokazatel, row.znachenie);
            }
            setTableData(mapData);
 
    }
 
}
/**
 * Пример построения отчета по данным из локальной базы sqlite. Источником
 * данных выступает внешняя таблица «Рапорт руководителю»
 *
 */
public class ToChiefReport extends SimpleMapHtmlReport {

	@Override
	public int getResIdIcon() {
        	return R.drawable.report_01;
	}

	@Override
	public int getResIdTitle() {
        	return R.string.report_name_to_chief;
	}

	@Override
	public void build(Context context, IReportBuilderResult builderResult) {

        	try {
                	makeReport(context);
        	} catch (SQLException e) {
                	e.printStackTrace();
                	setHeader1("Упс, отчет не создан!");
        	}

        	super.build(context, builderResult);
	}

	private void makeReport(Context context) throws SQLException {

        	DBHelper helper = new DBHelper(context);

        	// Заголовок отчета и таблицы
        	setHeader2("Финансовые показатели");
        	setTableHeader("Наименование ", "Сумма, руб.");

        	// Строки таблицы будут в порядке добавления
        	Map<Object, Object> mapData = new LinkedHashMap<Object, Object>();

        	// Выборка данных из локальной базы данных sqlite, внешняя таблица
        	// «Рапорт руководителю»
        	ExTableRaportRukovoditelyuDao dao = helper
                        	.getDao(ExTableRaportRukovoditelyu.class);
        	List<ExTableRaportRukovoditelyu> rows = dao.select();
        	for (ExTableRaportRukovoditelyu row : rows) {
                	mapData.put(row.pokazatel, row.znachenie);
        	}
        	setTableData(mapData);

	}

}

Прокомментирую код .  Абстрактный класс SimpleMapHtmlReport, от которого наследуется, предназначен для построения простого HTML отчета с таблицей из двух колонок.

Первые 2 метода getResIdIcon()  и getResIdTitle()  возвращают иконку и название нашего отчета для отображения в списке. В обработчике события «build»  вызывается метод makeReport,  в котором наша реализация компиляции отчета.

При построении отчета сначала устанавливается заголовок отчета и таблицы, затем все записи из локальной таблицы sqlite считываются как строки таблицы отчета. И, наконец, в методе setTableData() устанавливаем данные для таблицы отчета. Уф, как сложно комментировать очевидные вещи.

2 .Создаем второй отчет, здесь все еще проще:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * Пример скомпилированного отчета, просто выводит Html файл. В этом примере
 * данных файл сгенерирован в 1С и передан мобильному клиенту во время обмена
 *
 */
public class ProductsInStokReport extends SimpleCompiledHtmlReport {
 
        // Имя, под которым сохраняется полученный файл от 1с (см. процедуру обмена
        // MyExchangeTask)
        public static final String REPORT_FILE_NAME = "products_in_stok.html";
 
        public ProductsInStokReport(File file) {
                super(file);
        }
 
        @Override
        public int getResIdIcon() {
                return R.drawable.report_02;
        }
 
        @Override
        public int getResIdTitle() {
                return R.string.report_name_products_in_stok;
        }
 
}
/**
 * Пример скомпилированного отчета, просто выводит Html файл. В этом примере
 * данных файл сгенерирован в 1С и передан мобильному клиенту во время обмена
 *
 */
public class ProductsInStokReport extends SimpleCompiledHtmlReport {

    	// Имя, под которым сохраняется полученный файл от 1с (см. процедуру обмена
    	// MyExchangeTask)
    	public static final String REPORT_FILE_NAME = "products_in_stok.html";

    	public ProductsInStokReport(File file) {
            	super(file);
    	}

    	@Override
    	public int getResIdIcon() {
            	return R.drawable.report_02;
    	}

    	@Override
    	public int getResIdTitle() {
            	return R.string.report_name_products_in_stok;
    	}

}

3. Расширим предопределенную процедуру обмена с сервером 1С,  т.е добавим в нее еще один шаг – получение данных для нашего второго отчета в виде  скомпилированного файла.  Добавим класс MyExchangeTask наследник от ExchangeTask:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * Стандартная процедура обмена дополненная пользовательскими правилами.
 * Дополнительно получается один скомпилированный файл – отчет.
 *
 */
public class MyExchangeTask extends ExchangeTask {
 
        // Идентификатор отчета как он задан в 1С
        private static final String ID_REPORT_PRODUCTS_IN_STOK = "REPORT_PRODUCTS_IN_STOK";
 
        public MyExchangeTask(ExchangeVariant exchangeVariant, WSHelper wsrvHelper,
                        DBOpenHelper dbOpenHelper) {
                super(exchangeVariant, wsrvHelper, dbOpenHelper);
        }
 
        @Override
        protected boolean doExecute() throws Exception {
 
                // Выполнить шаги обмена по предопределенным правилам
                boolean success = super.doExecute();
                if (success) {
                        // Получить произвольные данные - наш 2-ой отчет
                        onStepInfo("Получаю отчеты…");
 
                        String fPath = appSettings.getCacheDir().getAbsolutePath() + "/"
                                        + ProductsInStokReport.REPORT_FILE_NAME;
                        File f = wsHelper.getLargeData(ID_REPORT_PRODUCTS_IN_STOK, null,
                                        "", fPath);
                        success = (f != null);
                }
 
                return success;
        }
 
}
/**
 * Стандартная процедура обмена дополненная пользовательскими правилами.
 * Дополнительно получается один скомпилированный файл – отчет.
 *
 */
public class MyExchangeTask extends ExchangeTask {

    	// Идентификатор отчета как он задан в 1С
    	private static final String ID_REPORT_PRODUCTS_IN_STOK = "REPORT_PRODUCTS_IN_STOK";

    	public MyExchangeTask(ExchangeVariant exchangeVariant, WSHelper wsrvHelper,
                    	DBOpenHelper dbOpenHelper) {
            	super(exchangeVariant, wsrvHelper, dbOpenHelper);
    	}

    	@Override
    	protected boolean doExecute() throws Exception {

            	// Выполнить шаги обмена по предопределенным правилам
            	boolean success = super.doExecute();
            	if (success) {
                    	// Получить произвольные данные - наш 2-ой отчет
                    	onStepInfo("Получаю отчеты…");

                    	String fPath = appSettings.getCacheDir().getAbsolutePath() + "/"
                                    	+ ProductsInStokReport.REPORT_FILE_NAME;
                    	File f = wsHelper.getLargeData(ID_REPORT_PRODUCTS_IN_STOK, null,
                                    	"", fPath);
                    	success = (f != null);
            	}

            	return success;
    	}

}

Для получения данных отчета используется метод веб-сервиса getLargeData, который в случае успеха сохраняет результат в файл по указанному пути. Обратите внимание на идентификатор ID_REPORT_PRODUCTS_IN_STOK, он должен быть таким же, как в 1С в методе ПодготовитьДвоичныеДанныеДляОтправки.

4. Вывод списка отчетов на главной форме. Откройте файл res\ activity_main.xml и добавьте listView в котором будет отображать список отчетов:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
 
    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true" >
    </ListView>
 
</RelativeLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

	<ListView
    	android:id="@android:id/list"
    	android:layout_width="match_parent"
    	android:layout_height="wrap_content"
    	android:layout_alignParentLeft="true"
    	android:layout_alignParentTop="true" >
	</ListView>

</RelativeLayout>

В MainActtiviy.java добавляем:

а) локальную переменную для списка

1
private ListView list;
private ListView list;

б) метод инициализации

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void init() {
        list = (ListView) findViewById(android.R.id.list);
 
        // Создать адаптер для отображения списка отчетов
        ReportListAdapter adapter = new ReportListAdapter(this,createReportList());
        list.setAdapter(adapter);
        list.setOnItemClickListener(new OnItemClickListener() {
 
                @Override
                public void onItemClick(AdapterView<?> parent, View view,       int position, long id) {
 
                        // Показываем отчет в диалоге по клику на нем
                        IReport report = (IReport) list.getItemAtPosition(position);
                        report.onShow(MainActivity.this);
                }
        });
}
private void init() {
    	list = (ListView) findViewById(android.R.id.list);

    	// Создать адаптер для отображения списка отчетов
    	ReportListAdapter adapter = new ReportListAdapter(this,createReportList());
    	list.setAdapter(adapter);
    	list.setOnItemClickListener(new OnItemClickListener() {

            	@Override
            	public void onItemClick(AdapterView<?> parent, View view,   	int position, long id) {

                    	// Показываем отчет в диалоге по клику на нем
                    	IReport report = (IReport) list.getItemAtPosition(position);
                    	report.onShow(MainActivity.this);
            	}
    	});
}

и вызов его в onCreate() после установки макета формы

1
2
setContentView(R.layout.activity_main);
init();
setContentView(R.layout.activity_main);
init();

в) метод инициализация списка отчетов

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* Подготовить список отчетов
*/
private List<IReport> createReportList() {
 
        List<IReport> lst = new ArrayList<IReport>();
 
        // Создать первый отчет, временный каталог где сохранен файл берем из
        // настроек
        IReport report = new ProductsInStokReport(new File(getAppSettings()
                        .getCacheDir(), ProductsInStokReport.REPORT_FILE_NAME));
        lst.add(report);
 
        // Создать и добавить в список прочие отчеты
        lst.add(new ToChiefReport());
}
/*
* Подготовить список отчетов
*/
private List<IReport> createReportList() {

    	List<IReport> lst = new ArrayList<IReport>();

    	// Создать первый отчет, временный каталог где сохранен файл берем из
    	// настроек
    	IReport report = new ProductsInStokReport(new File(getAppSettings()
                    	.getCacheDir(), ProductsInStokReport.REPORT_FILE_NAME));
    	lst.add(report);

    	// Создать и добавить в список прочие отчеты
    	lst.add(new ToChiefReport());
}

г) процедуру запуска обмена по вашим правилам

1
2
3
4
5
6
7
8
9
10
11
12
/*
* Запуск обмена по моим правилам. Обратите внимание, что в планировщике
* (если установлен) обмен остался по стандартным правилам
*/
private void startExchangeMyRules(ExchangeVariant variant,boolean cancelable) {
 
// хелпер для вызова методов web-сервиса
    WSHelper wsHelper = new WSHelper(getExchangeSettings());
    MyExchangeTask task = new MyExchangeTask(variant, wsHelper, getHelper());
 
    startExchange(task, cancelable);
}
/*
* Запуск обмена по моим правилам. Обратите внимание, что в планировщике
* (если установлен) обмен остался по стандартным правилам
*/
private void startExchangeMyRules(ExchangeVariant variant,boolean cancelable) {

// хелпер для вызова методов web-сервиса
   	WSHelper wsHelper = new WSHelper(getExchangeSettings());
   	MyExchangeTask task = new MyExchangeTask(variant, wsHelper, getHelper());

   	startExchange(task, cancelable);
}

И изменяем вызов процедуры обмена startExchange(ExchangeVariant.FULL, true) в обработчике onOptionsItemSelected на startExchangeMyRules(ExchangeVariant.FULL, true).

Приложение готово, формы авторизации и настроек программы будут созданы автоматически, изменять их не будем.

Давайте проверим его на эмуляторе. Если вы нигде не ошиблись, приложение запустится. Однако вместо окна авторизации будет отображено сразу окно настроек, так как не указаны данные авторизации (имя пользователя и пароль).

Укажите имя пользователя: ivanov
И пароль: 123456

Запустите базу 1С и откройте справочник «Мобильные сотрудники». В качестве мобильного устройства укажите предопределённый элемент «Эмулятор устройства», в табличную часть «Мобильные приложения» добавьте приложение «Рапорт руководителю» и установите идентичные имя и пароль пользователя.

Запустите на эмуляторе процедуру обмена (кнопка со стрелочкой «Вверх» на панели справа)

результат работы обмена выводится в уведомлении:

Нажмите на него для запуска вашего приложения (или для просмотра подробной информации об ошибке, если обмен завершился неудачно).

Проверим, что получилось. Кликните мышкой по отчету в списке:
   
Отлично! Отчеты выводятся, можно изменять масштаб по кнопке или жестами.

Дополнительно »

При конвертации отчета в HTML формат,  1С устанавливает фиксированную ширину колонок отчета. Такой отчет на мобильном устройстве не будет автоматически подгоняться под размеры экрана. Возможно, вам следует  подготовить макет отчета по ширине мобильного устройства или «обработать напильником» результат конвертации установив относительную ширину колонок, например, так:

1
2
3
4
5
6
<TABLE CELLSPACING=0 WIDTH=100%>
<COL WIDTH="55%">
<COL WIDTH="25%">
<COL WIDTH="20%"></TABLE>
<TABLE CELLSPACING=0 WIDTH=100%>
<COL WIDTH="55%">
<COL WIDTH="25%">
<COL WIDTH="20%">
…
</TABLE>

Приложение готово, получилось вполне функциональным и легко расширяемым. Добавление других отчетов не составит большого труда.

Вы можете передавать документы в различных форматах, например *.doc или *.pdf и использовать внешние приложения для их просмотра. Смотрите исходный код примера, там есть еще 3 отчета:

  • диаграмма с использованием  Google Chart Tools
  • график с использованием jQuery плагина Plot;
  • вывод PDF-отчета внешним приложением (PdfReport.java)

Детально описывать не буду, и так статья получилась большая. Если возникнут вопросы, готов ответить в комментариях.  Вот результат вывода:

   

Полный код примера вы можете скачать  по адресу https://xp-dev.com/svn/fba_toolkit_public/samples/fbaSample4ReportHead/

Использование HTML для построения отчетов позволяет создавать  весьма разнообразные отчеты, в т.ч. и с интерактивными элементами. Например, в одном нашем проекте используется вот такой отчет:

- для поля «Долг» используется условное цветовое оформление в зависимости от суммы;
- полный список неоплаченных накладных открывается только при нажатии на маркер «Все», по умолчанию отображаются только 4 последних.

И конечно вы ничем не ограничены, можете использовать сторонние библиотеки для вывода отчетов и диаграмм, например achartengine или самостоятельно создать свой график/диаграмму (примеров полно, в т.ч есть и в Android SDK).

Похожие записи: