diff --git a/src/main/java/com/heai/modules/pms/mapper/QcMapper.java b/src/main/java/com/heai/modules/pms/mapper/QcMapper.java index ee51d14..d6525be 100644 --- a/src/main/java/com/heai/modules/pms/mapper/QcMapper.java +++ b/src/main/java/com/heai/modules/pms/mapper/QcMapper.java @@ -94,8 +94,12 @@ public interface QcMapper { QcTemplateData selectItemDetails(QcTemplateData data); + List selectTemplateItemNos(QcTemplateData data); + void addItemDetails(QcTemplateData data); + void addItemDetailsBatch(@Param("list") List list); + void delItemDetails(QcTemplateData data); List getItemList(QcTemplateData data); @@ -211,8 +215,14 @@ public interface QcMapper { QcTemplateData selectPartAttrItemDetails(QcTemplateData data); + List selectPartAttrItemNos(QcTemplateData data); + + List selectTemplateItemsForSync(QcTemplateData data); + void insertPartAttrDetailed(QcTemplateData data); + void insertPartAttrDetailedBatch(@Param("list") List list); + void deletePartAttrDetailed(QcTemplateData data); void deletePartAttrDetailedAll(QcTemplateData data); diff --git a/src/main/java/com/heai/modules/pms/service/Impl/QcServiceImpl.java b/src/main/java/com/heai/modules/pms/service/Impl/QcServiceImpl.java index daff7ff..866d5b5 100644 --- a/src/main/java/com/heai/modules/pms/service/Impl/QcServiceImpl.java +++ b/src/main/java/com/heai/modules/pms/service/Impl/QcServiceImpl.java @@ -68,8 +68,10 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -335,6 +337,7 @@ public class QcServiceImpl implements QcService { List types = loadInspectionTypes(site); List programmes = loadSamplingProgrammes(site); List levels = loadSamplingLevels(site); + Map methodByNo = loadMethodByNo(site); List errors = new ArrayList<>(); List toSave = new ArrayList<>(); try (Workbook workbook = QcExcelImportHelper.openWorkbook(file)) { @@ -372,7 +375,7 @@ public class QcServiceImpl implements QcService { if (StringUtils.isBlank(item.getMethodNo())) { throw new RuntimeException("方法编码不能为空"); } - assertMethodExists(site, item.getMethodNo(), item.getInspectionTypeNo()); + assertMethodExists(item.getMethodNo(), item.getInspectionTypeNo(), methodByNo); item.setDefaultValue(blankToNull(QcExcelImportHelper.getString(row, headers, "参照值"))); item.setMaxValue(QcExcelImportHelper.getDecimal(row, headers, "最大值")); item.setMinValue(QcExcelImportHelper.getDecimal(row, headers, "最小值")); @@ -414,8 +417,10 @@ public class QcServiceImpl implements QcService { if (!errors.isEmpty()) { throw new RuntimeException("导入失败:
" + String.join("
", errors)); } + Map seqCursor = new HashMap<>(); for (QcItemData item : toSave) { - item.setItemNo(QcExcelImportHelper.buildTypePrefixNo(item.getInspectionTypeNo(), qcMapper.getItemNo(item))); + item.setItemNo(QcExcelImportHelper.buildTypePrefixNo(item.getInspectionTypeNo(), + nextImportSeq(seqCursor, item.getInspectionTypeNo(), 4, () -> qcMapper.getItemNo(item)))); qcMapper.qcItemSave(item); qcMapper.qcItemMethodSave(item); } @@ -474,8 +479,10 @@ public class QcServiceImpl implements QcService { if (!errors.isEmpty()) { throw new RuntimeException("导入失败:
" + String.join("
", errors)); } + Map seqCursor = new HashMap<>(); for (QcMethodData method : toSave) { - method.setMethodNo(QcExcelImportHelper.buildTypePrefixNo(method.getInspectionTypeNo(), qcMapper.getMethodNo(method))); + method.setMethodNo(QcExcelImportHelper.buildTypePrefixNo(method.getInspectionTypeNo(), + nextImportSeq(seqCursor, method.getInspectionTypeNo(), 4, () -> qcMapper.getMethodNo(method)))); qcMapper.qcMethodSave(method); } } @@ -575,20 +582,62 @@ public class QcServiceImpl implements QcService { throw new RuntimeException("检验水平不存在:" + value); } - private void assertMethodExists(String site, String methodNo, String inspectionTypeNo) { + private static final int IMPORT_BATCH_SIZE = 80; + + private Map loadMethodByNo(String site) { QcMethodData query = new QcMethodData(); query.setSite(site); - query.setMethodNo(methodNo); + Map map = new HashMap<>(); List list = qcMapper.queryMethodList(query); - QcMethodData found = null; if (list != null) { for (QcMethodData method : list) { - if (methodNo.equals(method.getMethodNo())) { - found = method; - break; + if (method != null && StringUtils.isNotBlank(method.getMethodNo())) { + map.putIfAbsent(method.getMethodNo().trim(), method); + } + } + } + return map; + } + + private String nextImportSeq(Map cursor, String typeNo, int width, Supplier initial) { + Integer next = cursor.get(typeNo); + if (next == null) { + String raw = initial == null ? null : initial.get(); + next = StringUtils.isBlank(raw) ? 1 : Integer.parseInt(raw.trim()); + } + cursor.put(typeNo, next + 1); + return String.format("%0" + width + "d", next); + } + + private void forEachImportBatch(List rows, Consumer> consumer) { + if (rows == null || rows.isEmpty()) { + return; + } + for (int i = 0; i < rows.size(); i += IMPORT_BATCH_SIZE) { + consumer.accept(rows.subList(i, Math.min(i + IMPORT_BATCH_SIZE, rows.size()))); + } + } + + private Set existingItemNos(Map> cache, String key, Supplier> loader) { + Set existing = cache.get(key); + if (existing != null) { + return existing; + } + existing = new HashSet<>(); + List nos = loader.get(); + if (nos != null) { + for (String itemNo : nos) { + if (StringUtils.isNotBlank(itemNo)) { + existing.add(itemNo.trim()); } } } + cache.put(key, existing); + return existing; + } + + private void assertMethodExists(String methodNo, String inspectionTypeNo, Map methods) { + QcMethodData found = methods == null ? null : methods.get(StringUtils.trimToEmpty(methodNo)); if (found == null) { throw new RuntimeException("检验方法不存在:" + methodNo); } @@ -1011,8 +1060,10 @@ public class QcServiceImpl implements QcService { if (!errors.isEmpty()) { throw new RuntimeException("导入失败:
" + String.join("
", errors)); } + Map seqCursor = new HashMap<>(); for (QcTemplateData task : toSave) { - task.setTemplateId(QcExcelImportHelper.buildTypePrefixNo(task.getInspectionTypeNo(), qcMapper.getTemplateNo(task))); + task.setTemplateId(QcExcelImportHelper.buildTypePrefixNo(task.getInspectionTypeNo(), + nextImportSeq(seqCursor, task.getInspectionTypeNo(), 3, () -> qcMapper.getTemplateNo(task)))); qcMapper.templateSave(task); } } @@ -1030,6 +1081,7 @@ public class QcServiceImpl implements QcService { Map templateCache = new HashMap<>(); Map itemCache = new HashMap<>(); Map orderByTemplate = new HashMap<>(); + Map> existingByTemplate = new HashMap<>(); Set excelKeys = new HashSet<>(); try (Workbook workbook = QcExcelImportHelper.openWorkbook(file)) { Sheet sheet = QcExcelImportHelper.getDataSheet(workbook, QcExcelImportHelper.FLAG_TEMPLATE_ITEM); @@ -1079,20 +1131,25 @@ public class QcServiceImpl implements QcService { && !template.getInspectionTypeNo().equals(item.getInspectionTypeNo())) { throw new RuntimeException("检验项目【" + itemNo + "】与模板检验类型不匹配"); } - QcTemplateData existQuery = new QcTemplateData(); - existQuery.setSite(StringUtils.defaultIfBlank(template.getSite(), site)); - existQuery.setBuNo(StringUtils.defaultIfBlank(template.getBuNo(), buNo)); - existQuery.setTemplateId(template.getTemplateId()); - existQuery.setItemNo(item.getItemNo()); - if (qcMapper.selectItemDetails(existQuery) != null) { + String detailSite = StringUtils.defaultIfBlank(template.getSite(), site); + String detailBu = StringUtils.defaultIfBlank(template.getBuNo(), buNo); + String resolvedTemplateId = template.getTemplateId(); + Set existingItems = existingItemNos(existingByTemplate, detailSite + "|" + detailBu + "|" + resolvedTemplateId, () -> { + QcTemplateData queryNos = new QcTemplateData(); + queryNos.setSite(detailSite); + queryNos.setBuNo(detailBu); + queryNos.setTemplateId(resolvedTemplateId); + return qcMapper.selectTemplateItemNos(queryNos); + }); + if (existingItems.contains(item.getItemNo())) { throw new RuntimeException("检验模板【" + template.getTemplateId() + "】下检验项目【" + item.getItemNo() + "】已存在"); } String excelDefault = QcExcelImportHelper.getString(row, headers, "标准值"); BigDecimal excelMax = QcExcelImportHelper.getDecimal(row, headers, "最大值"); BigDecimal excelMin = QcExcelImportHelper.getDecimal(row, headers, "最小值"); QcTemplateData detail = new QcTemplateData(); - detail.setSite(StringUtils.defaultIfBlank(template.getSite(), site)); - detail.setBuNo(StringUtils.defaultIfBlank(template.getBuNo(), buNo)); + detail.setSite(detailSite); + detail.setBuNo(detailBu); detail.setTemplateId(template.getTemplateId()); detail.setItemNo(item.getItemNo()); detail.setItemType("D"); @@ -1117,6 +1174,7 @@ public class QcServiceImpl implements QcService { } detail.setOrderId(nextOrder); orderByTemplate.put(templateId, nextOrder + 1); + existingItems.add(item.getItemNo()); toSave.add(detail); } catch (RuntimeException e) { errors.add("第" + excelRow + "行:" + e.getMessage()); @@ -1133,12 +1191,7 @@ public class QcServiceImpl implements QcService { if (!errors.isEmpty()) { throw new RuntimeException("导入失败:
" + String.join("
", errors)); } - for (QcTemplateData detail : toSave) { - if (qcMapper.selectItemDetails(detail) != null) { - throw new RuntimeException("检验模板【" + detail.getTemplateId() + "】下检验项目【" + detail.getItemNo() + "】已存在"); - } - qcMapper.addItemDetails(detail); - } + forEachImportBatch(toSave, qcMapper::addItemDetailsBatch); } @@ -1473,11 +1526,16 @@ public class QcServiceImpl implements QcService { * 保存物料属性模板关联,并将模板检验项目写入物料检验标准(已存在项目跳过) */ private void savePartAttributeDetailAndSync(QcTemplateData partData, Set syncedTemplateKeys) { + savePartAttributeDetailAndSync(partData, syncedTemplateKeys, new HashMap>()); + } + + private void savePartAttributeDetailAndSync(QcTemplateData partData, Set syncedTemplateKeys, + Map> templateItemCache) { qcMapper.savePartAttributeDetails(partData); String syncKey = partData.getSite() + "|" + partData.getBuNo() + "|" + partData.getAttributeNo() + "|" + partData.getTemplateId(); if (syncedTemplateKeys.add(syncKey)) { - syncPartAttrDetailedFromTemplate(partData); + syncPartAttrDetailedFromTemplate(partData, templateItemCache); } } @@ -1486,51 +1544,76 @@ public class QcServiceImpl implements QcService { */ @Override public void syncPartAttrDetailedFromTemplate(QcTemplateData partData) { + syncPartAttrDetailedFromTemplate(partData, new HashMap>()); + } + + private void syncPartAttrDetailedFromTemplate(QcTemplateData partData, Map> templateItemCache) { if (partData == null || StringUtils.isBlank(partData.getTemplateId()) || StringUtils.isBlank(partData.getAttributeNo())) { return; } - QcTemplateData query = new QcTemplateData(); - query.setSite(partData.getSite()); - query.setBuNo(partData.getBuNo()); - query.setTemplateId(partData.getTemplateId()); - query.setAttributeNo(partData.getAttributeNo()); - query.setItemType("D"); - List itemList = qcMapper.templateDetailsSearch(query); - if (itemList == null || itemList.isEmpty()) { + String cacheKey = StringUtils.defaultString(partData.getSite()) + "|" + partData.getTemplateId(); + List itemList = templateItemCache.get(cacheKey); + if (itemList == null) { + QcTemplateData query = new QcTemplateData(); + query.setSite(partData.getSite()); + query.setTemplateId(partData.getTemplateId()); + query.setItemType("D"); + itemList = qcMapper.selectTemplateItemsForSync(query); + if (itemList == null) { + itemList = Collections.emptyList(); + } + templateItemCache.put(cacheKey, itemList); + } + if (itemList.isEmpty()) { return; } - boolean inserted = false; + Set existing = new HashSet<>(); + List existingNos = qcMapper.selectPartAttrItemNos(partData); + if (existingNos != null) { + for (String itemNo : existingNos) { + if (StringUtils.isNotBlank(itemNo)) { + existing.add(itemNo.trim()); + } + } + } + Integer nextOrder = null; + List toInsert = new ArrayList<>(); for (QcTemplateData item : itemList) { - if (StringUtils.isBlank(item.getItemNo())) { + if (item == null || StringUtils.isBlank(item.getItemNo()) || existing.contains(item.getItemNo().trim())) { continue; } + if (nextOrder == null) { + nextOrder = qcMapper.getPartAttrDetailedMaxOrderId(partData); + if (nextOrder == null) { + nextOrder = 1; + } + } QcTemplateData attrItem = new QcTemplateData(); attrItem.setSite(partData.getSite()); attrItem.setBuNo(partData.getBuNo()); attrItem.setAttributeNo(partData.getAttributeNo()); attrItem.setItemNo(item.getItemNo()); - if (qcMapper.selectPartAttrItemDetails(attrItem) != null) { - continue; - } attrItem.setInspectionTypeNo(partData.getInspectionTypeNo()); attrItem.setDefaultValue(item.getDefaultValue()); attrItem.setMaxValue(item.getMaxValue()); attrItem.setMinValue(item.getMinValue()); attrItem.setObjectID(item.getObjectID()); - attrItem.setOrderId(qcMapper.getPartAttrDetailedMaxOrderId(attrItem)); - qcMapper.insertPartAttrDetailed(attrItem); - inserted = true; + attrItem.setOrderId(nextOrder++); + toInsert.add(attrItem); + existing.add(item.getItemNo().trim()); } - if (inserted) { - QcTemplateData timeData = new QcTemplateData(); - timeData.setSite(partData.getSite()); - timeData.setBuNo(partData.getBuNo()); - timeData.setAttributeNo(partData.getAttributeNo()); - timeData.setAttributeType(partData.getAttributeType()); - timeData.setUpdateBy(partData.getUpdateBy()); - qcMapper.updatePartAttributeTime(timeData); + if (toInsert.isEmpty()) { + return; } + forEachImportBatch(toInsert, qcMapper::insertPartAttrDetailedBatch); + QcTemplateData timeData = new QcTemplateData(); + timeData.setSite(partData.getSite()); + timeData.setBuNo(partData.getBuNo()); + timeData.setAttributeNo(partData.getAttributeNo()); + timeData.setAttributeType(partData.getAttributeType()); + timeData.setUpdateBy(partData.getUpdateBy()); + qcMapper.updatePartAttributeTime(timeData); } @Override @@ -1957,7 +2040,8 @@ public class QcServiceImpl implements QcService { } assertImportRows(toSave, errors); for (QcPartAttributeData task : toSave) { - qcPartAttributeSave(task); + qcMapper.qcPartAttributeSave(task); + qcMapper.syncPartExemptFlagsFromQcPartAttribute(task); } } @@ -1981,6 +2065,8 @@ public class QcServiceImpl implements QcService { Map customers = loadCustomerKeys(site); List programmes = loadSamplingProgrammes(site); List levels = loadSamplingLevels(site); + Map partAttrExists = new HashMap<>(); + Map templateCache = new HashMap<>(); List errors = new ArrayList<>(); List toSave = new ArrayList<>(); Set excelKeys = new HashSet<>(); @@ -2003,19 +2089,28 @@ public class QcServiceImpl implements QcService { if (StringUtils.isBlank(templateId)) { throw new RuntimeException("检验模板编码不能为空"); } - QcPartAttributeData partQuery = new QcPartAttributeData(); - partQuery.setSite(site); - partQuery.setBuNo(buNo); - partQuery.setPartNo(partNo); - partQuery.setAttributeType("A"); - if (qcMapper.selectPartAttributeByNo(partQuery) == null) { + Boolean partReady = partAttrExists.get(partNo); + if (partReady == null) { + QcPartAttributeData partQuery = new QcPartAttributeData(); + partQuery.setSite(site); + partQuery.setBuNo(buNo); + partQuery.setPartNo(partNo); + partQuery.setAttributeType("A"); + partReady = qcMapper.selectPartAttributeByNo(partQuery) != null; + partAttrExists.put(partNo, partReady); + } + if (!partReady) { throw new RuntimeException("物料属性不存在,请先导入物料属性:" + partNo); } - QcTemplateData templateQuery = new QcTemplateData(); - templateQuery.setSite(site); - templateQuery.setBuNo(buNo); - templateQuery.setTemplateId(templateId); - QcTemplateData template = qcMapper.selectTemplateData(templateQuery); + QcTemplateData template = templateCache.get(templateId); + if (!templateCache.containsKey(templateId)) { + QcTemplateData templateQuery = new QcTemplateData(); + templateQuery.setSite(site); + templateQuery.setBuNo(buNo); + templateQuery.setTemplateId(templateId); + template = qcMapper.selectTemplateData(templateQuery); + templateCache.put(templateId, template); + } if (template == null) { throw new RuntimeException("检验模板不存在:" + templateId); } @@ -2117,8 +2212,9 @@ public class QcServiceImpl implements QcService { } assertImportRows(toSave, errors); Set syncedTemplateKeys = new HashSet<>(); + Map> templateItemCache = new HashMap<>(); for (QcTemplateData detail : toSave) { - savePartAttributeDetailAndSync(detail, syncedTemplateKeys); + savePartAttributeDetailAndSync(detail, syncedTemplateKeys, templateItemCache); if (StringUtils.isNotBlank(detail.getIpqcInspectionMethod())) { qcMapper.updatePartAttributeInspectionMethod(detail); } @@ -2136,6 +2232,9 @@ public class QcServiceImpl implements QcService { List errors = new ArrayList<>(); List toSave = new ArrayList<>(); Map orderByPart = new HashMap<>(); + Map partAttrExists = new HashMap<>(); + Map itemCache = new HashMap<>(); + Map> existingByPart = new HashMap<>(); Set excelKeys = new HashSet<>(); try (Workbook workbook = QcExcelImportHelper.openWorkbook(file)) { Sheet sheet = QcExcelImportHelper.getDataSheet(workbook, QcExcelImportHelper.FLAG_PART_STANDARD); @@ -2159,24 +2258,35 @@ public class QcServiceImpl implements QcService { if (!excelKeys.add(partNo + "|" + itemNo)) { throw new RuntimeException("Excel中物料【" + partNo + "】的检验项目【" + itemNo + "】重复"); } - QcPartAttributeData partQuery = new QcPartAttributeData(); - partQuery.setSite(site); - partQuery.setBuNo(buNo); - partQuery.setPartNo(partNo); - partQuery.setAttributeType("A"); - if (qcMapper.selectPartAttributeByNo(partQuery) == null) { + Boolean partReady = partAttrExists.get(partNo); + if (partReady == null) { + QcPartAttributeData partQuery = new QcPartAttributeData(); + partQuery.setSite(site); + partQuery.setBuNo(buNo); + partQuery.setPartNo(partNo); + partQuery.setAttributeType("A"); + partReady = qcMapper.selectPartAttributeByNo(partQuery) != null; + partAttrExists.put(partNo, partReady); + } + if (!partReady) { throw new RuntimeException("物料属性不存在,请先导入物料属性:" + partNo); } - QcItemData item = qcMapper.selectQcItemByNo(site, itemNo); + QcItemData item = itemCache.get(itemNo); + if (item == null && !itemCache.containsKey(itemNo)) { + item = qcMapper.selectQcItemByNo(site, itemNo); + itemCache.put(itemNo, item); + } if (item == null) { throw new RuntimeException("检验项目不存在:" + itemNo); } - QcTemplateData existQuery = new QcTemplateData(); - existQuery.setSite(site); - existQuery.setBuNo(buNo); - existQuery.setAttributeNo(partNo); - existQuery.setItemNo(item.getItemNo()); - if (qcMapper.selectPartAttrItemDetails(existQuery) != null) { + Set existingItems = existingItemNos(existingByPart, partNo, () -> { + QcTemplateData queryNos = new QcTemplateData(); + queryNos.setSite(site); + queryNos.setBuNo(buNo); + queryNos.setAttributeNo(partNo); + return qcMapper.selectPartAttrItemNos(queryNos); + }); + if (existingItems.contains(item.getItemNo())) { throw new RuntimeException("物料【" + partNo + "】下检验项目【" + item.getItemNo() + "】已存在"); } String excelDefault = QcExcelImportHelper.getString(row, headers, "标准值"); @@ -2211,6 +2321,7 @@ public class QcServiceImpl implements QcService { } detail.setOrderId(nextOrder); orderByPart.put(partNo, nextOrder + 1); + existingItems.add(item.getItemNo()); toSave.add(detail); } catch (RuntimeException e) { errors.add("第" + excelRow + "行:" + e.getMessage()); @@ -2222,12 +2333,9 @@ public class QcServiceImpl implements QcService { throw new RuntimeException("导入失败:" + e.getMessage()); } assertImportRows(toSave, errors); + forEachImportBatch(toSave, qcMapper::insertPartAttrDetailedBatch); Set updatedParts = new HashSet<>(); for (QcTemplateData detail : toSave) { - if (qcMapper.selectPartAttrItemDetails(detail) != null) { - throw new RuntimeException("物料【" + detail.getAttributeNo() + "】下检验项目【" + detail.getItemNo() + "】已存在"); - } - qcMapper.insertPartAttrDetailed(detail); if (updatedParts.add(detail.getAttributeNo())) { qcMapper.updatePartAttributeTime(detail); } diff --git a/src/main/resources/mapper/pms/QcMapper.xml b/src/main/resources/mapper/pms/QcMapper.xml index 8e01728..750881f 100644 --- a/src/main/resources/mapper/pms/QcMapper.xml +++ b/src/main/resources/mapper/pms/QcMapper.xml @@ -578,12 +578,26 @@ WHERE ItemNo = #{itemNo} and ItemType = #{itemType} and site = #{site} + + INSERT INTO qc_template_detailed (template_id, ItemNo, default_value, max_value, min_value, site, bu_no,order_id) VALUES (#{templateId}, #{itemNo}, #{defaultValue}, #{maxValue,jdbcType=DECIMAL}, #{minValue,jdbcType=DECIMAL}, #{site}, #{buNo},#{orderId}) + + INSERT INTO qc_template_detailed (template_id, ItemNo, default_value, max_value, min_value, site, bu_no, order_id) + VALUES + + (#{item.templateId}, #{item.itemNo}, #{item.defaultValue}, #{item.maxValue,jdbcType=DECIMAL}, #{item.minValue,jdbcType=DECIMAL}, #{item.site}, #{item.buNo}, #{item.orderId}) + + + DELETE FROM qc_template_detailed @@ -723,6 +737,29 @@ AND item_no = #{itemNo} + + + + INSERT INTO qc_part_attr_detailed (site, bu_no, attribute_no, item_no, inspection_type_no, default_value, max_value, min_value, object_id, order_id, create_time) @@ -730,6 +767,15 @@ (#{site}, #{buNo}, #{attributeNo}, #{itemNo}, #{inspectionTypeNo}, #{defaultValue}, #{maxValue,jdbcType=DECIMAL}, #{minValue,jdbcType=DECIMAL}, #{objectID}, #{orderId}, getDate()) + + INSERT INTO qc_part_attr_detailed + (site, bu_no, attribute_no, item_no, inspection_type_no, default_value, max_value, min_value, object_id, order_id, create_time) + VALUES + + (#{item.site}, #{item.buNo}, #{item.attributeNo}, #{item.itemNo}, #{item.inspectionTypeNo}, #{item.defaultValue}, #{item.maxValue,jdbcType=DECIMAL}, #{item.minValue,jdbcType=DECIMAL}, #{item.objectID}, #{item.orderId}, getDate()) + + + DELETE FROM qc_part_attr_detailed WHERE site = #{site}