ru.apertum.qsystem.server.model.QService.java Source code

Java tutorial

Introduction

Here is the source code for ru.apertum.qsystem.server.model.QService.java

Source

/*
 *  Copyright (C) 2010 {Apertum}Projects. web: www.apertum.ru email: info@apertum.ru
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package ru.apertum.qsystem.server.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.Id;
import java.util.PriorityQueue;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.LinkedBlockingDeque;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.Transient;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import ru.apertum.qsystem.client.Locales;
import ru.apertum.qsystem.common.CustomerState;
import ru.apertum.qsystem.common.QLog;
import ru.apertum.qsystem.common.Uses;
import ru.apertum.qsystem.common.exceptions.ServerException;
import ru.apertum.qsystem.common.model.QCustomer;
import ru.apertum.qsystem.extra.ICustomerChangePosition;
import ru.apertum.qsystem.server.ServerProps;
import ru.apertum.qsystem.server.Spring;
import ru.apertum.qsystem.server.model.calendar.QCalendar;
import ru.apertum.qsystem.server.model.schedule.QBreak;
import ru.apertum.qsystem.server.model.schedule.QBreaks;
import ru.apertum.qsystem.server.model.schedule.QSchedule;

/**
 *   ? ?    ??: - ? ? -  ? -  ? -   
 * ??  .   ? ?    ? ?.  ?   ? ? ?   
 * 
 *
 * @author Evgeniy Egorov
 *
 */
@Entity
@Table(name = "services")
public class QService extends DefaultMutableTreeNode implements ITreeIdGetter, Transferable, Serializable {

    /**
     * ? ?, ?    ? ?
     */
    @Transient
    private final PriorityQueue<QCustomer> customers = new PriorityQueue<>();

    private PriorityQueue<QCustomer> getCustomers() {
        return customers;
    }

    @Transient
    //@Expose
    //@SerializedName("clients")
    private final LinkedBlockingDeque<QCustomer> clients = new LinkedBlockingDeque<>(customers);

    /**
     *  ? ? ??  ? ?   ??  ?   ?
     *
     * @return
     */
    public LinkedBlockingDeque<QCustomer> getClients() {
        return clients;
    }

    @Id
    @Column(name = "id")
    //@GeneratedValue(strategy = GenerationType.AUTO)  ?, .. id  ? ? 
    @Expose
    @SerializedName("id")
    private Long id = new Date().getTime();

    @Override
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    /**
     *  ? ? ? 
     */
    @Column(name = "deleted")
    @Temporal(javax.persistence.TemporalType.DATE)
    private Date deleted;

    public Date getDeleted() {
        return deleted;
    }

    public void setDeleted(Date deleted) {
        this.deleted = deleted;
    }

    /**
     * ?? ?. 1 - ?, 0 - ?, -1 - .
     */
    @Column(name = "status")
    @Expose
    @SerializedName("status")
    private Integer status;

    /**
     * ?? ?. ?  ???   ?,  
     *
     * @return 1 - ?, 0 - ?, -1 - , 2 -  ? , 3 - 
     */
    public Integer getStatus() {
        return status;
    }

    /**
     *  ?   . ?    ?   .  ?   ? ?, 0-? ?, -?
     * ? .
     */
    @Column(name = "point")
    @Expose
    @SerializedName("point")
    private Integer point = 0;

    public Integer getPoint() {
        return point;
    }

    public void setPoint(Integer point) {
        this.point = point;
    }

    /**
     * ?.  ? ? ? ?.  ? ? .  ?   ?.    .
     */
    @Column(name = "duration")
    @Expose
    @SerializedName("duration")
    private Integer duration = 1;

    public Integer getDuration() {
        return duration;
    }

    public void setDuration(Integer duration) {
        this.duration = duration;
    }

    /**
     * ? ? ? ??.
     */
    @Column(name = "expectation")
    @Expose
    @SerializedName("exp")
    private Integer expectation = 0;

    /**
     * ? ? ? ??.
     *
     * @return  
     */
    public Integer getExpectation() {
        return expectation;
    }

    public void setExpectation(Integer expectation) {
        this.expectation = expectation;
    }

    /**
     *   ?. null  0... - ? ?.      .
     */
    @Column(name = "sound_template")
    @Expose
    @SerializedName("sound_template")
    private String soundTemplate;

    public String getSoundTemplate() {
        return soundTemplate;
    }

    public void setSoundTemplate(String soundTemplate) {
        this.soundTemplate = soundTemplate;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    @Column(name = "advance_limit")
    @Expose
    @SerializedName("advance_limit")
    private Integer advanceLimit = 1;

    public Integer getAdvanceLimit() {
        return advanceLimit;
    }

    public void setAdvanceLinit(Integer advanceLimit) {
        this.advanceLimit = advanceLimit;
    }

    @Column(name = "day_limit")
    @Expose
    @SerializedName("day_limit")
    private Integer dayLimit = 0;

    public Integer getDayLimit() {
        return dayLimit;
    }

    public void setDayLimit(Integer dayLimit) {
        this.dayLimit = dayLimit;
    }

    @Column(name = "person_day_limit")
    @Expose
    @SerializedName("person_day_limit")
    private Integer personDayLimit = 0;

    public Integer getPersonDayLimit() {
        return personDayLimit;
    }

    public void setPersonDayLimit(Integer personDayLimit) {
        this.personDayLimit = personDayLimit;
    }

    /**
     *    ?,     ???    ?   null  0 ?  ?
     */
    @Column(name = "advance_limit_period")
    @Expose
    @SerializedName("advance_limit_period")
    private Integer advanceLimitPeriod = 0;

    public Integer getAdvanceLimitPeriod() {
        return advanceLimitPeriod;
    }

    public void setAdvanceLimitPeriod(Integer advanceLimitPeriod) {
        this.advanceLimitPeriod = advanceLimitPeriod;
    }

    /**
     *  ?  ?
     */
    @Column(name = "advance_time_period")
    @Expose
    @SerializedName("advance_time_period")
    private Integer advanceTimePeriod = 60;

    public Integer getAdvanceTimePeriod() {
        return advanceTimePeriod;
    }

    public void setAdvanceTimePeriod(Integer advanceTimePeriod) {
        this.advanceTimePeriod = advanceTimePeriod;
    }

    /**
     * ?    1 - ? 2 - backoffice, ..  ?    ,   
     */
    @Column(name = "enable")
    @Expose
    @SerializedName("enable")
    private Integer enable = 1;

    /**
     * ?    1 - ? 2 - backoffice, ..  ?    ,   
     *
     * @return int index
     */
    public Integer getEnable() {
        return enable;
    }

    public void setEnable(Integer enable) {
        this.enable = enable;
    }

    @Column(name = "seq_id")
    private Integer seqId = 0;

    public Integer getSeqId() {
        return seqId;
    }

    public void setSeqId(Integer seqId) {
        this.seqId = seqId;
    }

    /**
     *     ? ? ?  ?   ? ?   ?     ?? 
     */
    @Column(name = "result_required")
    @Expose
    @SerializedName("result_required")
    private Boolean result_required = false;

    public Boolean getResult_required() {
        return result_required;
    }

    public void setResult_required(Boolean result_required) {
        this.result_required = result_required;
    }

    /**
     *      ?    -   ?   ?  ?.
     */
    @Column(name = "input_required")
    @Expose
    @SerializedName("input_required")
    private Boolean input_required = false;

    public Boolean getInput_required() {
        return input_required;
    }

    public void setInput_required(Boolean input_required) {
        this.input_required = input_required;
    }

    /**
     * ?     ?      ,  ?   ?  ?  ,   
     * ?
     */
    @Column(name = "inputed_as_ext")
    @Expose
    @SerializedName("inputed_as_ext")
    private Boolean inputedAsExt = false;

    /**
     *      ?  ?(  ) .
     *
     * @return
     */
    public Boolean getInputedAsExt() {
        return inputedAsExt;
    }

    public void setInputedAsExt(Boolean inputedAsExt) {
        this.inputedAsExt = inputedAsExt;
    }

    /**
     *       ?  -   ?   ?  ?.  ??   ?
     * ?  .
     */
    @Column(name = "input_caption")
    @Expose
    @SerializedName("input_caption")
    private String input_caption = "";

    public String getInput_caption() {
        return input_caption;
    }

    public void setInput_caption(String input_caption) {
        this.input_caption = input_caption;
    }

    /**
     * html ?  ??  ?   ? ?  ?,   ??    
     *  ?
     */
    @Column(name = "pre_info_html")
    @Expose
    @SerializedName("pre_info_html")
    private String preInfoHtml = "";

    public String getPreInfoHtml() {
        return preInfoHtml;
    }

    public void setPreInfoHtml(String preInfoHtml) {
        this.preInfoHtml = preInfoHtml;
    }

    /**
     * ? ?   ?  ?  
     */
    @Column(name = "pre_info_print_text")
    @Expose
    @SerializedName("pre_info_print_text")
    private String preInfoPrintText = "";

    public String getPreInfoPrintText() {
        return preInfoPrintText;
    }

    public void setPreInfoPrintText(String preInfoPrintText) {
        this.preInfoPrintText = preInfoPrintText;
    }

    /**
     * ? ?   ?  ?  
     */
    @Column(name = "ticket_text")
    @Expose
    @SerializedName("ticket_text")
    private String ticketText = "";

    public String getTicketText() {
        return ticketText;
    }

    public void setTicketText(String ticketText) {
        this.ticketText = ticketText;
    }

    /**
     * ? ?            ?
     */
    @Column(name = "tablo_text")
    @Expose
    @SerializedName("tablo_text")
    private String tabloText = "";

    /**
     * ? ?            ?
     * @return ?  
     */
    public String getTabloText() {
        return tabloText;
    }

    public void setTabloText(String tabloText) {
        this.tabloText = tabloText;
    }

    /**
     * ?    ?
     */
    @Column(name = "but_x")
    @Expose
    @SerializedName("but_x")
    private Integer butX = 100;
    @Column(name = "but_y")
    @Expose
    @SerializedName("but_y")
    private Integer butY = 100;
    @Column(name = "but_b")
    @Expose
    @SerializedName("but_b")
    private Integer butB = 100;
    @Column(name = "but_h")
    @Expose
    @SerializedName("but_h")
    private Integer butH = 100;

    public Integer getButB() {
        return butB;
    }

    public void setButB(Integer butB) {
        this.butB = butB;
    }

    public Integer getButH() {
        return butH;
    }

    public void setButH(Integer butH) {
        this.butH = butH;
    }

    public Integer getButX() {
        return butX;
    }

    public void setButX(Integer butX) {
        this.butX = butX;
    }

    public Integer getButY() {
        return butY;
    }

    public void setButY(Integer butY) {
        this.butY = butY;
    }

    /**
     * ? ,  ? ?    ?  ?.   .  ? ?  json - 
     * ,    ?-?  ?    ?   ? .
     */
    @Transient
    private int lastNumber = Integer.MIN_VALUE;
    /**
     * ? ,  ? ?     ? ? ? ?.  ?   
     *   ?  ?    .
     */
    @Transient
    private static volatile int lastStNumber = Integer.MIN_VALUE;

    public QService() {
        super();
    }

    @Override
    public String toString() {
        return getName().trim().isEmpty() ? "<NO_NAME>" : getName();
    }

    /**
     *   ? ? ?.   ? .
     *
     * @return
     */
    public int getNextNumber() {
        synchronized (QService.class) {
            if (lastNumber == Integer.MIN_VALUE) {
                lastNumber = ServerProps.getInstance().getProps().getFirstNumber() - 1;
            }
            if (lastStNumber == Integer.MIN_VALUE) {
                lastStNumber = ServerProps.getInstance().getProps().getFirstNumber() - 1;
            }
            if (lastNumber >= ServerProps.getInstance().getProps().getLastNumber()) {
                clearNextNumber();
            }
            if (lastStNumber >= ServerProps.getInstance().getProps().getLastNumber()) {
                clearNextStNumber();
            }
            //   ?.    ? ??    ?
            final int today = new GregorianCalendar().get(GregorianCalendar.DAY_OF_YEAR);
            if (today != day) {
                day = today;
                setCountPerDay(0);
            }
            countPerDay++;

            // 0 - ? ?, 1 - ?  ? ?? ? 
            if (ServerProps.getInstance().getProps().getNumering()) {
                return ++lastNumber;
            } else {
                return ++lastStNumber;
            }
        }
    }

    //        ?  ?  ??  ? ?
    @Transient
    private int day_y = -100; // ? ? ? 
    @Transient
    private int dayAdvs = -100; // ? ? ? 

    /**
     *  ?  ? ? ? ?  
     *
     * @param date  ?   ? ? 
     * @param strictStart false - ? ? ?  ? , true - ? ?  ?  ? ?  date
     * @return ? ? 
     */
    public int getAdvancedCount(Date date, boolean strictStart) {
        final GregorianCalendar forDay = new GregorianCalendar();
        forDay.setTime(date);

        final GregorianCalendar today = new GregorianCalendar();
        if (!strictStart && forDay.get(GregorianCalendar.DAY_OF_YEAR) == today.get(GregorianCalendar.DAY_OF_YEAR)
                && day_y != today.get(GregorianCalendar.DAY_OF_YEAR)) {
            day_y = today.get(GregorianCalendar.DAY_OF_YEAR);
            dayAdvs = -100;
        }
        if (!strictStart && forDay.get(GregorianCalendar.DAY_OF_YEAR) == today.get(GregorianCalendar.DAY_OF_YEAR)
                && dayAdvs >= 0) {
            return dayAdvs;
        }

        final DetachedCriteria dc = DetachedCriteria.forClass(QAdvanceCustomer.class);
        dc.setProjection(Projections.rowCount());
        if (!strictStart) {
            forDay.set(GregorianCalendar.HOUR_OF_DAY, 0);
            forDay.set(GregorianCalendar.MINUTE, 0);
        }
        final Date today_m = forDay.getTime();
        forDay.set(GregorianCalendar.HOUR_OF_DAY, 23);
        forDay.set(GregorianCalendar.MINUTE, 59);
        dc.add(Restrictions.between("advanceTime", today_m, forDay.getTime()));
        dc.add(Restrictions.eq("service", this));
        final Long cnt = (Long) (Spring.getInstance().getHt().findByCriteria(dc).get(0));
        final int i = cnt.intValue();

        forDay.setTime(date);
        if (!strictStart && forDay.get(GregorianCalendar.DAY_OF_YEAR) == today.get(GregorianCalendar.DAY_OF_YEAR)) {
            dayAdvs = i;
        }

        QLog.l().logger()
                .trace("? ?  ??  "
                        + getName() + ".  " + i);
        return i;
    }

    /**
     * ???         ?  
     *
     * @param data
     * @return true - ,   ??? ?; false -    ?
     */
    public boolean isLimitPersonPerDayOver(String data) {
        final int today = new GregorianCalendar().get(GregorianCalendar.DAY_OF_YEAR);
        if (today != day) {
            day = today;
            setCountPerDay(0);
        }
        return getPersonDayLimit() != 0 && getPersonDayLimit() <= getCountPersonsPerDay(data);
    }

    private int getCountPersonsPerDay(String data) {
        int cnt = 0;
        cnt = customers.stream().filter((customer) -> (data.equalsIgnoreCase(customer.getInput_data())))
                .map((_item) -> 1).reduce(cnt, Integer::sum);
        if (getPersonDayLimit() <= cnt) {
            return cnt;
        }
        QLog.l().logger().trace(
                "   ? ?    \""
                        + data + "\"");
        //    ?
        final GregorianCalendar gc = new GregorianCalendar();
        gc.set(GregorianCalendar.HOUR, 0);
        gc.set(GregorianCalendar.MINUTE, 0);
        gc.set(GregorianCalendar.SECOND, 0);
        final Date start = gc.getTime();
        gc.add(GregorianCalendar.DAY_OF_YEAR, 1);
        final Date finish = gc.getTime();
        QLog.l().logger()
                .trace("FROM QCustomer a WHERE " + " start_time >'" + Uses.FORMAT_FOR_REP.format(start) + "' "
                        + " and start_time <= '" + Uses.FORMAT_FOR_REP.format(finish) + "' "
                        + " and  input_data = '" + data + "' " + " and service_id = " + getId());
        final List<QCustomer> custs = Spring.getInstance().getHt()
                .find("FROM QCustomer a WHERE " + " start_time >'" + Uses.FORMAT_FOR_REP.format(start) + "' "
                        + " and start_time <= '" + Uses.FORMAT_FOR_REP.format(finish) + "' "
                        + " and  input_data = '" + data + "' " + " and service_id = " + getId());
        QLog.l().logger().trace(
                "   ? ?    \""
                        + data + "\".  " + (cnt + custs.size()));
        return cnt + custs.size();
    }

    /**
     * ???        ?  
     *
     * @return true - ,   ??? ?; false -    ?
     */
    public boolean isLimitPerDayOver() {
        final Date now = new Date();
        int advCusts = getAdvancedCount(now, true); //? ?  ?    ??? ?(true)
        final int today = new GregorianCalendar().get(GregorianCalendar.DAY_OF_YEAR);
        if (today != day) {
            day = today;
            setCountPerDay(0);
        }
        final long p = getPossibleTickets();
        final long c = getCountPerDay();
        final boolean f = getDayLimit() != 0 && (p <= c + advCusts);
        if (f) {
            QLog.l().logger().trace("Customers overflow: DayLimit()=" + getDayLimit() + " && PossibleTickets=" + p
                    + " <= CountPerDay=" + c + " + advCusts=" + advCusts);
        }
        return f;// getDayLimit() != 0 && getPossibleTickets(now) <= getCountPerDay() + advCusts;
    }

    /**
     *  ? ,  ?    ?   ?  ?  
     *
     * @return ??? ?   ? /   ?  ?  
     */
    public long getPossibleTickets() {
        if (getDayLimit() != 0) {
            // ?    
            final GregorianCalendar gc = new GregorianCalendar();
            final Date now = new Date();
            gc.setTime(now);
            long dif = getSchedule().getWorkInterval(gc.getTime()).finish.getTime() - now.getTime();

            int ii = gc.get(GregorianCalendar.DAY_OF_WEEK) - 1;
            if (ii < 1) {
                ii = 7;
            }
            final QBreaks qb;
            switch (ii) {
            case 1:
                qb = getSchedule().getBreaks_1();
                break;
            case 2:
                qb = getSchedule().getBreaks_2();
                break;
            case 3:
                qb = getSchedule().getBreaks_3();
                break;
            case 4:
                qb = getSchedule().getBreaks_4();
                break;
            case 5:
                qb = getSchedule().getBreaks_5();
                break;
            case 6:
                qb = getSchedule().getBreaks_6();
                break;
            case 7:
                qb = getSchedule().getBreaks_7();
                break;
            default:
                throw new AssertionError();
            }
            if (qb != null) {//    
                for (QBreak br : qb.getBreaks()) {
                    if (br.getTo_time().after(now)) {
                        if (br.getFrom_time().before(now)) {
                            dif = dif - (br.getTo_time().getTime() - now.getTime());
                        } else {
                            dif = dif - br.diff();
                        }
                    }
                }
            }
            QLog.l().logger()
                    .trace("??   " + (dif / 1000 / 60)
                            + " . ?   " + getDayLimit()
                            + " ,  ??? ? "
                            + (dif / 1000 / 60 / getDayLimit()) + " ?.");
            return dif / 1000 / 60 / getDayLimit();
        } else {
            return Integer.MAX_VALUE;
        }
    }

    /**
     *  ?   ? ??
     */
    @Transient
    @Expose
    @SerializedName("countPerDay")
    private int countPerDay = 0;

    public void setCountPerDay(int countPerDay) {
        this.countPerDay = countPerDay;
    }

    public int getCountPerDay() {
        return countPerDay;
    }

    /**
     *  ,  ?  ? ?   ? ?   
     */
    @Transient
    @Expose
    @SerializedName("day")
    private int day = 0;

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    public void clearNextNumber() {
        lastNumber = ServerProps.getInstance().getProps().getFirstNumber() - 1;
    }

    public static void clearNextStNumber() {
        lastStNumber = ServerProps.getInstance().getProps().getFirstNumber() - 1;
    }

    public void addCustomerForRecoveryOnly(QCustomer customer) {
        if (customer.getPrefix() != null) {
            final int number = customer.getNumber();
            //     ? ?  ? ? ? ? 
            if (CustomerState.STATE_REDIRECT != customer.getState()
                    && CustomerState.STATE_WAIT_AFTER_POSTPONED != customer.getState()
                    && CustomerState.STATE_WAIT_COMPLEX_SERVICE != customer.getState()) {
                if (number > lastNumber) {
                    lastNumber = number;
                }
                if (number > lastStNumber) {
                    lastStNumber = number;
                }
            }
        }
        addCustomer(customer);
    }

    // ***************************************************************************************
    // ********************   ?? ??   ************************  
    // ***************************************************************************************
    /**
     *     ? ???  ??,   ?,   ?, ?  ?  ?,  ???  ?.
     *
     * @param customer ? ?  ?    ?
     */
    public void addCustomer(QCustomer customer) {
        if (customer.getPrefix() == null) {
            customer.setPrefix(getPrefix());
        }
        if (!getCustomers().add(customer)) {
            throw new ServerException(
                    "?   ?   ?.");
        }

        //  ??? /    
        QCustomer before = null;
        QCustomer after = null;
        for (Iterator<QCustomer> itr = getCustomers().iterator(); itr.hasNext();) {
            final QCustomer c = itr.next();
            if (!customer.getId().equals(c.getId())) {
                if (customer.compareTo(c) == 1) {
                    // c - , ? before
                    if (before == null) {
                        before = c;
                    } else if (before.compareTo(c) == -1) {
                        before = c;
                    }
                } else if (customer.compareTo(c) != 0) {
                    // c - ?, ? after
                    if (after == null) {
                        after = c;
                    } else if (after.compareTo(c) == 1) {
                        after = c;
                    }
                }
            }
        }
        //  ??? 
        for (final ICustomerChangePosition event : ServiceLoader.load(ICustomerChangePosition.class)) {
            QLog.l().logger()
                    .info(" SPI ??. ?: " + event.getDescription());
            event.insert(customer, before, after);
        }

        clients.clear();
        clients.addAll(getCustomers());
    }

    /**
     * ? , ? ?!
     */
    public void freeCustomers() {
        //  ??? 
        for (final ICustomerChangePosition event : ServiceLoader.load(ICustomerChangePosition.class)) {
            QLog.l().logger()
                    .info(" SPI ??. ?: " + event.getDescription());
            for (Iterator<QCustomer> itr = getCustomers().iterator(); itr.hasNext();) {
                event.remove(itr.next());
            }
        }
        getCustomers().clear();
        clients.clear();
        clients.addAll(getCustomers());
    }

    /**
     * ,   ?. NoSuchElementException  
     *
     * @return    ?
     */
    public QCustomer getCustomer() {
        return getCustomers().element();
    }

    /**
     *   . NoSuchElementException  
     *
     * @return    ?
     */
    public QCustomer removeCustomer() {
        final QCustomer customer = getCustomers().remove();

        //  ??? 
        for (final ICustomerChangePosition event : ServiceLoader.load(ICustomerChangePosition.class)) {
            QLog.l().logger()
                    .info(" SPI ??. ?: " + event.getDescription());
            event.remove(customer);
        }

        clients.clear();
        clients.addAll(getCustomers());
        return customer;
    }

    /**
     *    ?. null  
     *
     * @return    ?
     */
    public QCustomer peekCustomer() {
        return getCustomers().peek();
    }

    /**
     *   .   null  
     *
     * @return    ?
     */
    public QCustomer polCustomer() {
        final QCustomer customer = getCustomers().poll();
        if (customer != null) {
            //  ??? 
            for (final ICustomerChangePosition event : ServiceLoader.load(ICustomerChangePosition.class)) {
                QLog.l().logger()
                        .info(" SPI ??. ?: " + event.getDescription());
                event.remove(customer);
            }
        }

        clients.clear();
        clients.addAll(getCustomers());
        return customer;
    }

    /**
     *     ?.
     *
     * @param customer ? ?
     * @return   false  
     */
    public boolean removeCustomer(QCustomer customer) {
        final Boolean res = getCustomers().remove(customer);
        if (customer != null && res) {
            //  ??? 
            for (final ICustomerChangePosition event : ServiceLoader.load(ICustomerChangePosition.class)) {
                QLog.l().logger()
                        .info(" SPI ??. ?: " + event.getDescription());
                event.remove(customer);
            }
        }
        clients.clear();
        clients.addAll(getCustomers());
        return res;
    }

    /**
     *  ? ?, ??  .
     *
     * @return ? ?  ? ?
     */
    public int getCountCustomers() {
        return getCustomers().size();

    }

    public boolean changeCustomerPriorityByNumber(String number, int newPriority) {
        for (QCustomer customer : getCustomers()) {
            if (number.equalsIgnoreCase(customer.getPrefix() + customer.getNumber())) {
                customer.setPriority(newPriority);
                removeCustomer(customer); //   
                addCustomer(customer);// ?  ? ??
                return true;
            }
        }
        return false;
    }

    public QCustomer gnawOutCustomerByNumber(String number) {
        for (QCustomer customer : getCustomers()) {
            if (number.equalsIgnoreCase(customer.getPrefix() + customer.getNumber())) {
                removeCustomer(customer); //   
                return customer;
            }
        }
        return null;
    }

    /**
     * ? ?.
     */
    @Expose
    @SerializedName("description")
    @Column(name = "description")
    private String description;

    public void setDescription(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    /**
     * ? ?.
     */
    @Expose
    @SerializedName("service_prefix")
    @Column(name = "service_prefix")
    private String prefix = "";

    public void setPrefix(String prefix) {
        this.prefix = prefix == null ? "" : prefix;
    }

    public String getPrefix() {
        return prefix == null ? "" : prefix;
    }

    /**
     * ? ?.
     */
    @Expose
    @SerializedName("name")
    @Column(name = "name")
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    /**
     * ??   ?.
     */
    @Expose
    @SerializedName("buttonText")
    @Column(name = "button_text")
    private String buttonText;

    public String getButtonText() {
        return buttonText;
    }

    public void setButtonText(String buttonText) {
        this.buttonText = buttonText;
    }

    /**
     *  ?.
     */
    @Expose
    @SerializedName("parentId")
    @Column(name = "prent_id")
    private Long parentId;

    @Override
    public Long getParentId() {
        return parentId;
    }

    public void setParentId(Long parentId) {
        this.parentId = parentId;
    }

    @OneToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    @JoinColumn(name = "link_service_id")
    private QService link;

    public QService getLink() {
        return link;
    }

    public void setLink(QService link) {
        this.link = link;
    }

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    @JoinColumn(name = "schedule_id")
    private QSchedule schedule;

    public QSchedule getSchedule() {
        return schedule;
    }

    public void setSchedule(QSchedule schedule) {
        this.schedule = schedule;
    }

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    @JoinColumn(name = "calendar_id")
    private QCalendar calendar;

    public QCalendar getCalendar() {
        return calendar;
    }

    public void setCalendar(QCalendar calendar) {
        this.calendar = calendar;
    }

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
    @JoinColumn(name = "services_id")
    @Expose
    @SerializedName("langs")
    private Set<QServiceLang> langs = new HashSet<>();

    public Set<QServiceLang> getLangs() {
        return langs;
    }

    public void setLangs(Set<QServiceLang> langs) {
        this.langs = langs;
    }

    @Transient
    private HashMap<String, QServiceLang> qslangs = null;

    public QServiceLang getServiceLang(String nameLocale) {
        if (qslangs == null) {
            qslangs = new HashMap<>();
            getLangs().stream().forEach((sl) -> {
                qslangs.put(sl.getLang(), sl);
            });
        }
        return qslangs.get(nameLocale);
    }

    public static enum Field {

        /**
         * ??  
         */
        BUTTON_TEXT,
        /**
         *   
         */
        INPUT_CAPTION,
        /**
         *     ?  
         */
        PRE_INFO_HTML,
        /**
         *  ?    ?  
         */
        PRE_INFO_PRINT_TEXT,
        /**
         * ?   ? ?
         */
        TICKET_TEXT,
        /**
         * ? ?
         */
        DESCRIPTION,
        /**
         * ? ?
         */
        NAME
    };

    public String getTextToLocale(Field field) {
        final String nl = Locales.getInstance().getNameOfPresentLocale();
        final QServiceLang sl = getServiceLang(nl);
        switch (field) {
        case BUTTON_TEXT:
            return !Locales.getInstance().isWelcomeMultylangs() || sl == null || sl.getButtonText() == null
                    || sl.getButtonText().isEmpty() ? getButtonText() : sl.getButtonText();
        case INPUT_CAPTION:
            return !Locales.getInstance().isWelcomeMultylangs() || sl == null || sl.getInput_caption() == null
                    || sl.getInput_caption().isEmpty() ? getInput_caption() : sl.getInput_caption();
        case PRE_INFO_HTML:
            return !Locales.getInstance().isWelcomeMultylangs() || sl == null || sl.getPreInfoHtml() == null
                    || sl.getPreInfoHtml().isEmpty() ? getPreInfoHtml() : sl.getPreInfoHtml();
        case PRE_INFO_PRINT_TEXT:
            return !Locales.getInstance().isWelcomeMultylangs() || sl == null || sl.getPreInfoPrintText() == null
                    || sl.getPreInfoPrintText().isEmpty() ? getPreInfoPrintText() : sl.getPreInfoPrintText();
        case TICKET_TEXT:
            return !Locales.getInstance().isWelcomeMultylangs() || sl == null || sl.getTicketText() == null
                    || sl.getTicketText().isEmpty() ? getTicketText() : sl.getTicketText();
        case DESCRIPTION:
            return !Locales.getInstance().isWelcomeMultylangs() || sl == null || sl.getDescription() == null
                    || sl.getDescription().isEmpty() ? getDescription() : sl.getDescription();
        case NAME:
            return !Locales.getInstance().isWelcomeMultylangs() || sl == null || sl.getName() == null
                    || sl.getName().isEmpty() ? getName() : sl.getName();
        default:
            throw new AssertionError();
        }

    }

    /**
     * ?  NULL   ??,  ? ? ?  ?  ?      ? ?   ?
     */
    @Transient
    private String tempReasonUnavailable;

    public String getTempReasonUnavailable() {
        return tempReasonUnavailable;
    }

    public void setTempReasonUnavailable(String tempReasonUnavailable) {
        this.tempReasonUnavailable = tempReasonUnavailable;
    }

    //*******************************************************************************************************************
    //*******************************************************************************************************************
    //********************** ?     *********************************************************** 
    /**
     *  ?  ? ?   ? .     ? ?.
     */
    @Transient
    private QService parentService;
    @Transient
    @Expose
    @SerializedName("inner_services")
    private final LinkedList<QService> childrenOfService = new LinkedList<>();

    public LinkedList<QService> getChildren() {
        return childrenOfService;
    }

    @Override
    public void addChild(ITreeIdGetter child) {
        if (!childrenOfService.contains((QService) child)) { //   ?  
            childrenOfService.add((QService) child);
        }
    }

    @Override
    public QService getChildAt(int childIndex) {
        return childrenOfService.get(childIndex);
    }

    @Override
    public int getChildCount() {
        return childrenOfService.size();
    }

    @Override
    public QService getParent() {
        return parentService;
    }

    @Override
    public int getIndex(TreeNode node) {
        return childrenOfService.indexOf(node);
    }

    @Override
    public boolean getAllowsChildren() {
        return true;
    }

    @Override
    public boolean isLeaf() {
        return getChildCount() == 0;
    }

    @Override
    public Enumeration children() {
        return Collections.enumeration(childrenOfService);
    }

    @Override
    public void insert(MutableTreeNode child, int index) {
        child.setParent(this);
        this.childrenOfService.add(index, (QService) child);
    }

    @Override
    public void remove(int index) {
        this.childrenOfService.remove(index);
    }

    @Override
    public void remove(MutableTreeNode node) {
        this.childrenOfService.remove((QService) node);
    }

    @Override
    public void removeFromParent() {
        getParent().remove(getParent().getIndex(this));
    }

    @Override
    public void setParent(MutableTreeNode newParent) {
        parentService = (QService) newParent;
        if (parentService != null) {
            setParentId(parentService.id);
        } else {
            parentId = null;
        }
    }

    /**
     * data flavor used to get back a DnDNode from data transfer
     */
    public static final DataFlavor DND_NODE_FLAVOR = new DataFlavor(QService.class, "Drag and drop Node");
    /**
     * list of all flavors that this DnDNode can be transfered as
     */
    protected static DataFlavor[] flavors = { QService.DND_NODE_FLAVOR };

    @Override
    public DataFlavor[] getTransferDataFlavors() {
        return flavors;
    }

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        return true;
    }

    @Override
    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
        if (this.isDataFlavorSupported(flavor)) {
            return this;
        } else {
            throw new UnsupportedFlavorException(flavor);
        }
    }
}