package org.multibit.mbm.core.model;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.xeiam.xchange.currency.MoneyUtils;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.joda.money.BigMoney;
import org.multibit.mbm.utils.ObjectUtils;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Map;
import java.util.Set;
/**
* <p>DTO to provide the following to the application</p>
* <ul>
* <li>Provision of persistent state</li>
* </ul>
* <p>A Item provides the central link for all the aspects that come together to describe a product for sale.</p>
*/
@Entity
@Table(name = "items")
public class Item implements Serializable {
private static final long serialVersionUID = 38947590324750L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id = null;
/**
* <p>The <a href="http://en.wikipedia.org/wiki/Stock-keeping_unit">stock-keeping unit</a></p>
* <p>Provides a mandatory code to identify an item using a local arbitrary structure, e.g. "ABC-123". The GTIN
* value could be replicated here if appropriate.</p>
*/
@Column(name = "sku", nullable = false)
private String sku = null;
/**
* <p>The <a href="http://en.wikipedia.org/wiki/Global_Trade_Item_Number">global trade item number</a></p>
* <p>Provides the optional GTIN code to identify an item using international standards such as UPC, EAN and IAN.
* In the case of books, ISBN is compatible with the EAN-13 standard.</p>
*/
@Column(name = "gtin", nullable = true)
private String gtin = null;
/**
* Indicates if the User has been deleted (archived)
*/
@Column(name = "deleted", nullable = false)
private boolean deleted = false;
/**
* Provides a reason for being deleted
*/
@Column(name = "reasonForDelete", nullable = true)
private String reasonForDelete = null;
@OneToMany(
targetEntity = CartItem.class,
cascade = {CascadeType.ALL},
mappedBy = "primaryKey.item",
fetch = FetchType.EAGER,
orphanRemoval = true
)
private Set<CartItem> cartItems = Sets.newLinkedHashSet();
// TODO An Item has many prices depending on date, volume, discount, premium etc
@Columns(columns = {@Column(name = "amount"), @Column(name = "currency")})
@Type(type = "org.multibit.mbm.db.dao.hibernate.type.BigMoneyType")
private BigMoney localPrice = MoneyUtils.parseBitcoin("BTC 0.0000");
// TODO An Item has many tax rates depending on date, seller etc
@Column(name = "taxRate", nullable = false)
private double taxRate = 0.0;
/**
* This collection is effectively the fields for the Item so must be eager
*/
@OneToMany(
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true
)
@MapKeyEnumerated
private Map<ItemField, ItemFieldDetail> itemFieldMap = Maps.newLinkedHashMap();
/*
* Default constructor required for Hibernate
*/
public Item() {
}
/**
* Required
*
* @return The internal unique ID
*/
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
/**
* Required
*
* @return The stock-keeping unit reference (see http://en.wikipedia.org/wiki/Stock-keeping_unit)
*/
public String getSKU() {
return sku;
}
public void setSKU(String sku) {
this.sku = sku;
}
/**
* Optional
*
* @return The global trade item number (see http://en.wikipedia.org/wiki/Global_Trade_Item_Number)
*/
public String getGTIN() {
return gtin;
}
public void setGTIN(String gtin) {
this.gtin = gtin;
}
/**
* @return The tax rate applicable to this Item
*/
public double getTaxRate() {
return taxRate;
}
public void setTaxRate(double taxRate) {
this.taxRate = taxRate;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public String getReasonForDelete() {
return reasonForDelete;
}
public void setReasonForDelete(String reasonForDelete) {
this.reasonForDelete = reasonForDelete;
}
/**
* The CartItem instances that bind Cart and Item<br>
* Cascade occurs on the owner side
*
* @return Returns cartItems
*/
public Set<CartItem> getCartItems() {
return cartItems;
}
public void setCartItems(Set<CartItem> cartItems) {
this.cartItems = cartItems;
}
/**
* @return The price per item in the local currency
*/
public BigMoney getLocalPrice() {
return localPrice;
}
public void setLocalPrice(BigMoney localPrice) {
this.localPrice = localPrice;
}
/**
* @return The raw {@link ItemField} map
*/
public Map<ItemField, ItemFieldDetail> getItemFieldMap() {
return itemFieldMap;
}
public void setItemFieldMap(Map<ItemField, ItemFieldDetail> itemFieldMap) {
this.itemFieldMap = itemFieldMap;
}
/**
* Utility method to assist locating default entries for a given field
*
* @param itemField The item field (e.g. "SUMMARY", "INSTRUCTIONS")
*
* @return The localised content for the default locale, or null if it does not exist
*/
@Transient
public String getItemFieldContent(ItemField itemField) {
if (itemFieldMap.containsKey(itemField)) {
LocalisedText localisedText = itemFieldMap.get(itemField).getPrimaryDetail();
if (localisedText != null) {
return localisedText.getContent();
}
}
return null;
}
/**
* Utility method to assist locating default entries for a given locale
*
* @param itemField The item field (e.g. "SUMMARY", "INSTRUCTIONS")
* @param localeKey The locale key (e.g. "en" or "en_US")
*
* @return The localised content for the given locale, or null if it does not exist
*/
@Transient
public String getItemFieldContent(ItemField itemField, String localeKey) {
// Get the field detail that contains the primary and secondary content
ItemFieldDetail itemFieldDetail = getItemFieldDetail(itemField);
if (itemFieldDetail != null) {
// Attempt to locate the content for the given locale key
// Always check the primary field first
LocalisedText localisedText = itemFieldDetail.getPrimaryDetail();
if (localisedText != null) {
if (localisedText.getLocaleKey().equalsIgnoreCase(localeKey)) {
// The primary detail has a matching locale
return localisedText.getContent();
}
// Primary failed, so examine the secondary detail
if (!itemFieldDetail.getSecondaryDetails().isEmpty()) {
for (LocalisedText secondary : itemFieldDetail.getSecondaryDetails()) {
if (secondary.getLocaleKey().equalsIgnoreCase(localeKey)) {
// Return the first match
return secondary.getContent();
}
}
} // No matching content in secondary
} // No localised text
} // Parameters are invalid
return null;
}
/**
* Utility method to assist locating particular entries
*
* @param itemField The item field (e.g. "SUMMARY", "INSTRUCTIONS")
*
* @return The {@link ItemFieldDetail} providing the information, or null if none available
*/
@Transient
public ItemFieldDetail getItemFieldDetail(ItemField itemField) {
return itemFieldMap.get(itemField);
}
/**
* Utility method to assist locating particular entries
*
* @param itemField The item field (e.g. "SUMMARY", "INSTRUCTIONS")
* @param itemFieldDetail The contact method details providing the email address, or VOIP address etc
*/
@Transient
public void setItemFieldDetail(ItemField itemField, ItemFieldDetail itemFieldDetail) {
itemFieldMap.put(itemField, itemFieldDetail);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Item other = (Item) obj;
return ObjectUtils.isEqual(
id, other.id,
sku, other.sku,
gtin, other.gtin
);
}
@Override
public int hashCode() {
return ObjectUtils.getHashCode(id, sku);
}
@Override
public String toString() {
return String.format("Item[id=%s, SKU='%s', GTIN='%s']]", id, sku, gtin);
}
}