/*
* This program is part of the OpenLMIS logistics management information
* system platform software.
*
* Copyright © 2015 ThoughtWorks, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. This program is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details. You should
* have received a copy of the GNU Affero General Public License along with
* this program. If not, see http://www.gnu.org/licenses. For additional
* information contact info@OpenLMIS.org
*/
package org.openlmis.core.service;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.openlmis.core.LMISApp;
import org.openlmis.core.R;
import org.openlmis.core.exceptions.LMISException;
import org.openlmis.core.manager.SharedPreferenceMgr;
import org.openlmis.core.manager.UserInfoMgr;
import org.openlmis.core.model.Product;
import org.openlmis.core.model.StockCard;
import org.openlmis.core.model.repository.ProductProgramRepository;
import org.openlmis.core.model.repository.ProductRepository;
import org.openlmis.core.model.repository.ProgramDataFormRepository;
import org.openlmis.core.model.repository.ProgramRepository;
import org.openlmis.core.model.repository.RnrFormRepository;
import org.openlmis.core.model.repository.StockRepository;
import org.openlmis.core.model.service.StockService;
import org.openlmis.core.network.LMISRestApi;
import org.openlmis.core.network.model.ProductAndSupportedPrograms;
import org.openlmis.core.network.model.SyncDownLatestProductsResponse;
import org.openlmis.core.network.model.SyncDownProgramDataResponse;
import org.openlmis.core.network.model.SyncDownRequisitionsResponse;
import org.openlmis.core.network.model.SyncDownStockCardResponse;
import org.openlmis.core.utils.DateUtil;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
@Singleton
public class SyncDownManager {
private static final int DAYS_OF_MONTH = 30;
private static final int MONTHS_OF_YEAR = 12;
private boolean isSyncing = false;
protected LMISRestApi lmisRestApi;
@Inject
SharedPreferenceMgr sharedPreferenceMgr;
@Inject
RnrFormRepository rnrFormRepository;
@Inject
StockRepository stockRepository;
@Inject
ProgramRepository programRepository;
@Inject
ProductRepository productRepository;
@Inject
ProductProgramRepository productProgramRepository;
@Inject
ProgramDataFormRepository programDataFormRepository;
@Inject
StockService stockService;
public SyncDownManager() {
lmisRestApi = LMISApp.getInstance().getRestApi();
}
public void syncDownServerData(Subscriber<SyncProgress> subscriber) {
if (isSyncing) {
return;
}
isSyncing = true;
Observable.create(new Observable.OnSubscribe<SyncProgress>() {
@Override
public void call(Subscriber<? super SyncProgress> subscriber) {
try {
syncDownProducts(subscriber);
syncDownLastMonthStockCards(subscriber);
syncDownRequisition(subscriber);
syncDownRapidTests(subscriber);
syncDownLastYearStockCardsSilently(subscriber);
isSyncing = false;
subscriber.onCompleted();
} catch (LMISException e) {
isSyncing = false;
subscriber.onError(e);
}
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);
}
private void syncDownRapidTests(Subscriber<? super SyncProgress> subscriber) throws LMISException {
if (!sharedPreferenceMgr.isRapidTestDataSynced()) {
try {
subscriber.onNext(SyncProgress.SyncingRapidTests);
fetchAndSaveRapidTests();
sharedPreferenceMgr.setRapidTestsDataSynced(true);
subscriber.onNext(SyncProgress.RapidTestsSynced);
} catch (LMISException e) {
sharedPreferenceMgr.setRapidTestsDataSynced(false);
e.reportToFabric();
throw new LMISException(errorMessage(R.string.msg_sync_rapid_tests_failed));
}
}
}
private void fetchAndSaveRapidTests() throws LMISException {
SyncDownProgramDataResponse syncDownProgramDataResponse = lmisRestApi.fetchProgramDataForms(Long.parseLong(UserInfoMgr.getInstance().getUser().getFacilityId()));
if (syncDownProgramDataResponse == null) {
throw new LMISException("Can't get SyncDownRapidTestsResponse, you can check json parse to POJO logic");
}
programDataFormRepository.batchSaveForms(syncDownProgramDataResponse.getProgramDataForms());
}
public void syncDownServerData() {
syncDownServerData(new Subscriber<SyncProgress>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(SyncProgress syncProgress) {
}
});
}
private void syncDownLastYearStockCardsSilently(Subscriber<? super SyncProgress> subscriber) {
if (sharedPreferenceMgr.shouldSyncLastYearStockData()) {
try {
subscriber.onNext(SyncProgress.SyncingStockCardsLastYear);
fetchLatestYearStockMovements();
sharedPreferenceMgr.setShouldSyncLastYearStockCardData(false);
stockService.immediatelyUpdateAvgMonthlyConsumption();
subscriber.onNext(SyncProgress.StockCardsLastYearSynced);
} catch (LMISException e) {
sharedPreferenceMgr.setShouldSyncLastYearStockCardData(true);
e.reportToFabric();
}
}
}
private void syncDownRequisition(Subscriber<? super SyncProgress> subscriber) throws LMISException {
if (!sharedPreferenceMgr.isRequisitionDataSynced()) {
try {
subscriber.onNext(SyncProgress.SyncingRequisition);
fetchAndSaveRequisition();
sharedPreferenceMgr.setRequisitionDataSynced(true);
subscriber.onNext(SyncProgress.RequisitionSynced);
} catch (LMISException e) {
sharedPreferenceMgr.setRequisitionDataSynced(false);
e.reportToFabric();
throw new LMISException(errorMessage(R.string.msg_sync_requisition_failed));
}
}
}
private void syncDownLastMonthStockCards(Subscriber<? super SyncProgress> subscriber) throws LMISException {
if (!sharedPreferenceMgr.isLastMonthStockDataSynced()) {
try {
subscriber.onNext(SyncProgress.SyncingStockCardsLastMonth);
fetchLatestOneMonthMovements();
sharedPreferenceMgr.setLastMonthStockCardDataSynced(true);
sharedPreferenceMgr.setShouldSyncLastYearStockCardData(true);
subscriber.onNext(SyncProgress.StockCardsLastMonthSynced);
} catch (LMISException e) {
sharedPreferenceMgr.setLastMonthStockCardDataSynced(false);
e.reportToFabric();
throw new LMISException(errorMessage(R.string.msg_sync_stock_movement_failed));
}
}
}
private void syncDownProducts(Subscriber<? super SyncProgress> subscriber) throws LMISException {
try {
subscriber.onNext(SyncProgress.SyncingProduct);
fetchAndSaveProductsWithProgramsAndKits();
subscriber.onNext(SyncProgress.ProductSynced);
} catch (LMISException e) {
e.reportToFabric();
throw new LMISException(errorMessage(R.string.msg_sync_products_list_failed));
}
}
private void fetchAndSaveProductsWithProgramsAndKits() throws LMISException {
SyncDownLatestProductsResponse response = getSyncDownLatestProductResponse();
List<Product> productList = new ArrayList<>();
for (ProductAndSupportedPrograms productAndSupportedPrograms : response.getLatestProducts()) {
Product product = productAndSupportedPrograms.getProduct();
productProgramRepository.batchSave(productAndSupportedPrograms.getProductPrograms());
updateDeactivateProductNotifyList(product);
productList.add(product);
}
productRepository.batchCreateOrUpdateProducts(productList);
sharedPreferenceMgr.setLastSyncProductTime(response.getLatestUpdatedTime());
}
protected void updateDeactivateProductNotifyList(Product product) throws LMISException {
Product existingProduct = productRepository.getByCode(product.getCode());
if (existingProduct == null) {
return;
}
if (product.isActive() == existingProduct.isActive()) {
return;
}
if (product.isActive()) {
sharedPreferenceMgr.removeShowUpdateBannerTextWhenReactiveProduct(existingProduct.getPrimaryName());
return;
}
StockCard stockCard = stockRepository.queryStockCardByProductId(existingProduct.getId());
if (stockCard == null) {
return;
}
if (stockCard.getProduct().isArchived()) {
return;
}
if (stockCard.getStockOnHand() == 0) {
sharedPreferenceMgr.setIsNeedShowProductsUpdateBanner(true, product.getPrimaryName());
}
}
private SyncDownLatestProductsResponse getSyncDownLatestProductResponse() throws LMISException {
return lmisRestApi.fetchLatestProducts(sharedPreferenceMgr.getLastSyncProductTime());
}
private void fetchAndSaveStockCards(String startDate, String endDate) throws LMISException {
//default start date is one month before and end date is one day after
final String facilityId = UserInfoMgr.getInstance().getUser().getFacilityId();
SyncDownStockCardResponse syncDownStockCardResponse = lmisRestApi.fetchStockMovementData(facilityId, startDate, endDate);
stockRepository.batchCreateSyncDownStockCardsAndMovements(syncDownStockCardResponse.getStockCards());
}
private void fetchAndSaveRequisition() throws LMISException {
SyncDownRequisitionsResponse syncDownRequisitionsResponse = lmisRestApi.fetchRequisitions(UserInfoMgr.getInstance().getUser().getFacilityCode());
if (syncDownRequisitionsResponse == null) {
throw new LMISException("Can't get SyncDownRequisitionsResponse, you can check json parse to POJO logic");
}
rnrFormRepository.createRnRsWithItems(syncDownRequisitionsResponse.getRequisitions());
}
private void fetchLatestOneMonthMovements() throws LMISException {
Date now = new Date();
Date startDate = DateUtil.minusDayOfMonth(now, DAYS_OF_MONTH);
String startDateStr = DateUtil.formatDate(startDate, DateUtil.DB_DATE_FORMAT);
Date endDate = DateUtil.addDayOfMonth(now, 1);
String endDateStr = DateUtil.formatDate(endDate, DateUtil.DB_DATE_FORMAT);
fetchAndSaveStockCards(startDateStr, endDateStr);
List<StockCard> syncedStockCard = stockRepository.list();
if (!(syncedStockCard == null || syncedStockCard.isEmpty())) {
sharedPreferenceMgr.setIsNeedsInventory(false);
}
}
private void fetchLatestYearStockMovements() throws LMISException {
long syncEndTimeMillions = sharedPreferenceMgr.getPreference().getLong(SharedPreferenceMgr.KEY_STOCK_SYNC_END_TIME, new Date().getTime());
Date now = new Date(syncEndTimeMillions);
int startMonth = sharedPreferenceMgr.getPreference().getInt(SharedPreferenceMgr.KEY_STOCK_SYNC_CURRENT_INDEX, 1);
for (int month = startMonth; month <= MONTHS_OF_YEAR; month++) {
Date startDate = DateUtil.minusDayOfMonth(now, DAYS_OF_MONTH * (month + 1));
String startDateStr = DateUtil.formatDate(startDate, DateUtil.DB_DATE_FORMAT);
Date endDate = DateUtil.minusDayOfMonth(now, DAYS_OF_MONTH * month);
String endDateStr = DateUtil.formatDate(endDate, DateUtil.DB_DATE_FORMAT);
try {
fetchAndSaveStockCards(startDateStr, endDateStr);
} catch (LMISException e) {
sharedPreferenceMgr.getPreference().edit().putLong(SharedPreferenceMgr.KEY_STOCK_SYNC_END_TIME, syncEndTimeMillions).apply();
sharedPreferenceMgr.getPreference().edit().putInt(SharedPreferenceMgr.KEY_STOCK_SYNC_CURRENT_INDEX, month).apply();
throw e;
}
}
}
private String errorMessage(int code) {
return LMISApp.getContext().getResources().getString(code);
}
public enum SyncProgress {
SyncingProduct(R.string.msg_fetching_products),
SyncingStockCardsLastMonth(R.string.msg_sync_stock_movements_data),
SyncingRequisition(R.string.msg_sync_requisition_data),
SyncingStockCardsLastYear,
SyncingRapidTests,
ProductSynced,
StockCardsLastMonthSynced,
RequisitionSynced,
StockCardsLastYearSynced,
RapidTestsSynced;
private int messageCode;
SyncProgress(int messageCode) {
this.messageCode = messageCode;
}
SyncProgress() {
}
public int getMessageCode() {
return messageCode;
}
}
}