Merge pull request 'add-store' (#27) from add-store into main

Reviewed-on: #27
po-online-status
usama.jameel 2025-05-29 06:58:33 +00:00
commit 98a4f33f5a
29 changed files with 746 additions and 63 deletions

View File

@ -0,0 +1,14 @@
package com.utopiaindustries.auth;
import org.springframework.security.access.prepost.PreAuthorize;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@PreAuthorize( "hasAnyRole('ROLE_STORE','ROLE_ADMIN')")
public @interface StoreRole {
}

View File

@ -35,22 +35,22 @@ public class DataSourceConfiguration {
/* COSMOS */
@Bean(name = "dataSourceCosmos")
@ConfigurationProperties(prefix = "spring.cosmosdatasource")
public DataSource cosmosDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "jdbcTemplateCosmos")
public JdbcTemplate cosmosJdbcTemplate( @Qualifier( "dataSourceCosmos" ) DataSource ds ) {
return new JdbcTemplate( ds );
}
@Bean(name = "namedParameterJdbcTemplateCosmos")
public NamedParameterJdbcTemplate cosmosNamedParameterJdbcTemplate( @Qualifier( "dataSourceCosmos" ) DataSource ds ) {
return new NamedParameterJdbcTemplate( ds );
}
// @Bean(name = "dataSourceCosmos")
// @ConfigurationProperties(prefix = "spring.cosmosdatasource")
// public DataSource cosmosDataSource() {
// return DataSourceBuilder.create().build();
// }
//
//
// @Bean(name = "jdbcTemplateCosmos")
// public JdbcTemplate cosmosJdbcTemplate( @Qualifier( "dataSourceCosmos" ) DataSource ds ) {
// return new JdbcTemplate( ds );
// }
//
// @Bean(name = "namedParameterJdbcTemplateCosmos")
// public NamedParameterJdbcTemplate cosmosNamedParameterJdbcTemplate( @Qualifier( "dataSourceCosmos" ) DataSource ds ) {
// return new NamedParameterJdbcTemplate( ds );
// }
/* LOCAL */
@ -73,6 +73,4 @@ public class DataSourceConfiguration {
public NamedParameterJdbcTemplate localNamedParameterJdbcTemplate( @Qualifier( "dataSourceLocal" ) DataSource ds ) {
return new NamedParameterJdbcTemplate( ds );
}
}

View File

@ -35,6 +35,7 @@ public class PackagingController {
@GetMapping("/receive-inventory")
public String packagingItemReceive( Model model ){
model.addAttribute("accounts", inventoryAccountService.findInventoryAccounts(6L));
model.addAttribute("wrapper", new FinishedItemWrapper() );
return "/packaging/receive-inventory-form";
}

View File

@ -34,6 +34,11 @@ public class ReportingController {
this.inventoryAccountService = inventoryAccountService;
}
@GetMapping
public String homePage( Model model ){
return "redirect:/reporting/po-report";
}
@GetMapping( "/summary")
public String summary(@RequestParam(value = "item-id", required = false ) String itemId, @RequestParam(value = "sku" , required = false) String sku, @RequestParam(value = "start-date", required = false) String startDate, @RequestParam(value = "end-date", required = false) String endDate, Model model ){

View File

@ -0,0 +1,72 @@
package com.utopiaindustries.controller;
import com.utopiaindustries.auth.StoreRole;
import com.utopiaindustries.model.ctp.FinishedItemWrapper;
import com.utopiaindustries.service.InventoryAccountService;
import com.utopiaindustries.service.LocationService;
import com.utopiaindustries.service.StoreService;
import com.utopiaindustries.util.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@StoreRole
@RequestMapping( "/store" )
public class StoreController {
private final InventoryAccountService inventoryAccountService;
private final StoreService storeService;
private final LocationService locationService;
public StoreController(InventoryAccountService inventoryAccountService, StoreService storeService, LocationService locationService) {
this.inventoryAccountService = inventoryAccountService;
this.storeService = storeService;
this.locationService = locationService;
}
@GetMapping
public String showHome(Model model ){
return "redirect:/store/receive-inventory";
}
@GetMapping("/receive-inventory")
public String packagingItemReceive( Model model ){
model.addAttribute("accounts", inventoryAccountService.findInventoryAccounts(9L));
model.addAttribute("wrapper", new FinishedItemWrapper() );
return "/store/receive-inventory-form";
}
@PostMapping( "/store-items" )
public String packagingItems( @ModelAttribute FinishedItemWrapper wrapper,
RedirectAttributes redirectAttributes,
Model model ){
try {
storeService.createStoreItems( wrapper );
redirectAttributes.addFlashAttribute("success", "Items Successfully received !" );
} catch ( Exception e ){
redirectAttributes.addFlashAttribute("error", e.getMessage() );
}
return "redirect:/store/receive-inventory";
}
@GetMapping( "/inventory-accounts" )
public String showInventoryAccounts(@RequestParam( value = "id", required = false ) String id,
@RequestParam( value = "title", required = false) String title,
@RequestParam( value = "active", required = false ) String active,
@RequestParam( value = "created-by", required = false ) String createdBy,
@RequestParam( value = "start-date", required = false ) String startDate,
@RequestParam( value = "end-date", required = false ) String endDate,
@RequestParam( value = "site-id", required = false ) String siteId,
@RequestParam( value = "count", required = false ) Long count,
Model model ){
if(StringUtils.isNullOrEmpty( active )){
return "redirect:/store/inventory-accounts?id=&title=&active=1&created-by=&start-date=&end-date=&site-id=&site-title=&count=100";
}
model.addAttribute("accounts", inventoryAccountService.findInventoryAccounts(9L));
model.addAttribute("locations", locationService.findAll() );
return "/store/inventory-accounts";
}
}

View File

@ -21,15 +21,15 @@ public class FinishedItemDAO {
private final String TABLE_NAME = "cut_to_pack.finished_item";
private final String SELECT_QUERY = String.format("SELECT * FROM %s WHERE id = :id", TABLE_NAME);
private final String SELECT_ALL_QUERY = String.format("SELECT * FROM %s ORDER BY id DESC", TABLE_NAME);
private final String SELECT_QUERY_BY_BARCODE_QA_STATUS = String.format("SELECT case when EXISTS ( SELECT * FROM %s WHERE barcode = :barcode AND (qa_status = 'APPROVED' OR qa_status = 'WASHED') ) then true else false End as Result", TABLE_NAME);
private final String SELECT_QUERY_BY_BARCODE_QA_STATUS =String.format("SELECT CASE WHEN EXISTS (SELECT 1 FROM %s WHERE barcode = :barcode AND (is_store = TRUE OR is_packed = TRUE)) THEN TRUE ELSE FALSE END AS result", TABLE_NAME);
private final String SELECT_QUERY_BY_JOB_CARD = String.format("SELECT * FROM %s WHERE job_card_id = :job_card_id", TABLE_NAME);
private final String DELETE_QUERY = String.format("DELETE FROM %s WHERE id = :id", TABLE_NAME);
private final String INSERT_QUERY = String.format("INSERT INTO %s (id, item_id, sku, barcode, created_at, created_by, job_card_id, is_qa, stitched_item_id, is_segregated, qa_status, is_packed, operation_date) VALUES (:id, :item_id, :sku, :barcode, :created_at, :created_by, :job_card_id, :is_qa, :stitched_item_id, :is_segregated, :qa_status, :is_packed, :operation_date) ON DUPLICATE KEY UPDATE item_id = VALUES(item_id), sku = VALUES(sku), barcode = VALUES(barcode), created_at = VALUES(created_at), created_by = VALUES(created_by), job_card_id = VALUES(job_card_id), is_qa = VALUES(is_qa), stitched_item_id = VALUES(stitched_item_id), is_segregated = VALUES(is_segregated), qa_status = VALUES(qa_status), is_packed = VALUES(is_packed), operation_date = VALUES(operation_date) ", TABLE_NAME);
private final String INSERT_QUERY = String.format("INSERT INTO %s (id, item_id, sku, barcode, created_at, created_by, job_card_id, is_qa, stitched_item_id, is_segregated, qa_status, is_packed, operation_date, is_store) VALUES (:id, :item_id, :sku, :barcode, :created_at, :created_by, :job_card_id, :is_qa, :stitched_item_id, :is_segregated, :qa_status, :is_packed, :operation_date, :is_store) ON DUPLICATE KEY UPDATE item_id = VALUES(item_id), sku = VALUES(sku), barcode = VALUES(barcode), created_at = VALUES(created_at), created_by = VALUES(created_by), job_card_id = VALUES(job_card_id), is_qa = VALUES(is_qa), stitched_item_id = VALUES(stitched_item_id), is_segregated = VALUES(is_segregated), qa_status = VALUES(qa_status), is_packed = VALUES(is_packed), operation_date = VALUES(operation_date), is_store = VALUES(is_store)", TABLE_NAME);
private final String SELECT_BY_LIMIT = String.format("SELECT * FROM %s ORDER BY id DESC LIMIT :limit", TABLE_NAME);
private final String SELECT_BY_IDS = String.format("SELECT * FROM %s WHERE id IN (:ids)", TABLE_NAME);
private final String FIND_TOTAL_COUNT = String.format("SELECT COUNT(*) FROM %s where job_card_id = :job_card_id And item_id = :item_id", TABLE_NAME);
private final String SELECT_BY_TERM = String.format("SELECT * FROM %s WHERE barcode LIKE :term AND is_qa = :is_qa AND is_packed = FALSE ORDER BY ID DESC", TABLE_NAME);
private final String SELECT_BY_TERM_FOR_PACKAGING = String.format("SELECT * FROM %s WHERE barcode LIKE :term AND is_segregated = :is_segregated AND qa_status = 'APPROVED' AND is_packed = FALSE ORDER BY ID DESC", TABLE_NAME);
private final String SELECT_BY_TERM = String.format("SELECT * FROM %s WHERE barcode LIKE :term AND is_qa = :is_qa AND is_packed = FALSE AND is_store = FALSE ORDER BY ID DESC", TABLE_NAME);
private final String SELECT_BY_TERM_FOR_PACKAGING = String.format("SELECT * FROM %s WHERE barcode LIKE :term AND is_segregated = :is_segregated AND qa_status = :qa_status AND is_packed = FALSE AND is_store = FALSE ORDER BY ID DESC", TABLE_NAME);
private final String SELECT_BY_STITCHED_ITEM_ID = String.format("SELECT * FROM %s WHERE stitched_item_id = :stitched_item_id AND is_packed = FALSE", TABLE_NAME);
private final String SELECT_BY_STITCHED_ITEM_IDS = String.format("SELECT * FROM %s WHERE stitched_item_id IN (:stitched_item_ids)", TABLE_NAME);
private final String COUNT_TOTAL_FINISH_ITEM = String.format("SELECT COUNT(*) FROM %s WHERE job_card_id = :job_card_id AND is_segregated IS TRUE ", TABLE_NAME);
@ -55,6 +55,7 @@ public class FinishedItemDAO {
.addValue("is_segregated", finishedItem.getIsSegregated())
.addValue("qa_status", finishedItem.getQaStatus())
.addValue("is_packed", finishedItem.isPackaging())
.addValue("is_store", finishedItem.isStore())
.addValue("operation_date", finishedItem.getOperationDate());
return params;
}
@ -123,10 +124,11 @@ public class FinishedItemDAO {
return namedParameterJdbcTemplate.query(SELECT_BY_TERM, params, new FinishedItemRowMapper());
}
public List<FinishedItem> findByTermForPackaging(String term, boolean isSegregated) {
public List<FinishedItem> findByTermForPackaging(String term, boolean isSegregated, String qaStatus) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("term", "%" + term + "%");
params.addValue("is_segregated", isSegregated);
params.addValue("qa_status", qaStatus);
return namedParameterJdbcTemplate.query(SELECT_BY_TERM_FOR_PACKAGING, params, new FinishedItemRowMapper());
}

View File

@ -26,6 +26,7 @@ public class FinishedItemRowMapper implements RowMapper<FinishedItem> {
finishedItem.setIsSegregated( rs.getBoolean( "is_segregated") );
finishedItem.setQaStatus( rs.getString("qa_status" ) );
finishedItem.setPackaging( rs.getBoolean("is_packed" ) );
finishedItem.setStore( rs.getBoolean("is_store" ) );
return finishedItem;
}
}

View File

@ -0,0 +1,107 @@
package com.utopiaindustries.dao.ctp;
import com.utopiaindustries.model.ctp.PackagingItems;
import com.utopiaindustries.model.ctp.StoreItem;
import com.utopiaindustries.util.KeyHolderFunctions;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
@Repository
public class StoreItemDao {
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private static final String TABLE_NAME = "store_items";
private static final String SELECT_BY_ID = String.format("SELECT * FROM %s WHERE id = :id", TABLE_NAME);
private static final String SELECT_ALL = String.format("SELECT * FROM %s ORDER BY id DESC", TABLE_NAME);
private static final String DELETE_BY_ID = String.format("DELETE FROM %s WHERE id = :id", TABLE_NAME);
private static final String SELECT_BY_JOB_CARD_ID = String.format("SELECT * FROM %s WHERE job_card_id = :job_card_id", TABLE_NAME);
private static final String INSERT_QUERY = String.format(
"INSERT INTO %s (" +
"id, item_id, sku, barcode, job_card_id, created_at, created_by, " +
"finish_item_id, account_id, bundle_id" +
") VALUES (" +
":id, :item_id, :sku, :barcode, :job_card_id, :created_at, :created_by, " +
":finish_item_id, :account_id, :bundle_id" +
") ON DUPLICATE KEY UPDATE " +
"item_id = VALUES(item_id), sku = VALUES(sku), barcode = VALUES(barcode), " +
"job_card_id = VALUES(job_card_id), created_at = VALUES(created_at), created_by = VALUES(created_by), " +
"finish_item_id = VALUES(finish_item_id), account_id = VALUES(account_id), bundle_id = VALUES(bundle_id)",
TABLE_NAME
);
private static final String SELECT_BY_DATE_AND_IDs = String.format(
"SELECT COUNT(*) FROM %s WHERE (:start_date IS NULL OR created_at >= :start_date) AND created_at <= :end_date AND id IN (:ids)",
TABLE_NAME
);
public StoreItemDao(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
private MapSqlParameterSource prepareParams(StoreItem item) {
return new MapSqlParameterSource()
.addValue("id", item.getId())
.addValue("item_id", item.getItemId())
.addValue("sku", item.getSku())
.addValue("barcode", item.getBarcode())
.addValue("job_card_id", item.getJobCardId())
.addValue("created_at", item.getCreatedAt())
.addValue("created_by", item.getCreatedBy())
.addValue("finish_item_id", item.getFinishedItemId())
.addValue("account_id", item.getAccountId())
.addValue("bundle_id", item.getBundleId());
}
public StoreItem find(long id) {
MapSqlParameterSource params = new MapSqlParameterSource("id", id);
return namedParameterJdbcTemplate.query(SELECT_BY_ID, params, new StoreItemRowMapper())
.stream().findFirst().orElse(new StoreItem());
}
public List<StoreItem> findAll() {
return namedParameterJdbcTemplate.query(SELECT_ALL, new StoreItemRowMapper());
}
public long save(StoreItem item) {
KeyHolder keyHolder = new GeneratedKeyHolder();
MapSqlParameterSource params = prepareParams(item);
namedParameterJdbcTemplate.update(INSERT_QUERY, params, keyHolder);
return KeyHolderFunctions.getKey(item.getId(), keyHolder);
}
public int[] saveAll(List<StoreItem> items) {
List<MapSqlParameterSource> batchParams = new ArrayList<>();
for (StoreItem item : items) {
batchParams.add(prepareParams(item));
}
return namedParameterJdbcTemplate.batchUpdate(INSERT_QUERY, batchParams.toArray(new MapSqlParameterSource[0]));
}
public boolean delete(long id) {
MapSqlParameterSource params = new MapSqlParameterSource("id", id);
return namedParameterJdbcTemplate.update(DELETE_BY_ID, params) > 0;
}
public List<StoreItem> findByJobCardId(long jobCardId) {
MapSqlParameterSource params = new MapSqlParameterSource("job_card_id", jobCardId);
return namedParameterJdbcTemplate.query(SELECT_BY_JOB_CARD_ID, params, new StoreItemRowMapper());
}
public Long findByDateAndIds(String startDate, String endDate, List<Long> ids) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("start_date", startDate);
params.addValue("end_date", endDate);
params.addValue("ids", ids);
Long count = namedParameterJdbcTemplate.queryForObject(SELECT_BY_DATE_AND_IDs, params, Long.class);
return count != null ? count : 0;
}
}

View File

@ -0,0 +1,26 @@
package com.utopiaindustries.dao.ctp;
import com.utopiaindustries.model.ctp.PackagingItems;
import com.utopiaindustries.model.ctp.StoreItem;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public class StoreItemRowMapper implements RowMapper<StoreItem> {
@Override
public StoreItem mapRow(ResultSet rs, int rowNum) throws SQLException {
StoreItem item = new StoreItem();
item.setId(rs.getLong("id"));
item.setItemId(rs.getLong("item_id"));
item.setSku(rs.getString("sku"));
item.setBarcode(rs.getString("barcode"));
item.setJobCardId(rs.getLong("job_card_id"));
item.setCreatedAt(rs.getTimestamp("created_at") != null ? rs.getTimestamp("created_at").toLocalDateTime() : null);
item.setCreatedBy(rs.getString("created_by"));
item.setFinishedItemId(rs.getLong("finish_item_id"));
item.setAccountId(rs.getLong("account_id"));
item.setBundleId(rs.getLong("bundle_id"));
return item;
}
}

View File

@ -11,5 +11,6 @@ public enum Roles {
ROLE_PACKAGING,
ROLE_REPORTING,
ROLE_PURCHASE_ORDER,
ROLE_INVENTORY_ACCOUNT
ROLE_INVENTORY_ACCOUNT,
ROLE_STORE
}

View File

@ -24,6 +24,7 @@ public class FinishedItem implements InventoryArtifact {
private long accountId;
private String qaStatus;
private boolean isPackaging;
private boolean isStore;
@DateTimeFormat( pattern = "yyyy-MM-dd HH:mm:ss" )
private LocalDateTime operationDate;
@ -173,6 +174,14 @@ public class FinishedItem implements InventoryArtifact {
this.operationDate = operationDate;
}
public boolean isStore() {
return isStore;
}
public void setStore(boolean store) {
isStore = store;
}
@Override
public String toString() {
return "FinishedItem{" +

View File

@ -5,6 +5,7 @@ import java.util.List;
public class FinishedItemWrapper {
private String qaStatus;
private Long accountId;
private List<FinishedItem> items;
@ -24,6 +25,14 @@ public class FinishedItemWrapper {
this.qaStatus = qaStatus;
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
@Override
public String toString() {
return "FinishedItemWrapper{" +

View File

@ -5,6 +5,7 @@ public enum InventoryArtifactType {
STITCHING_OFFLINE,
FINISHED_ITEM,
STITCH_BUNDLE,
PACKAGING
PACKAGING,
STORED_ITEM,
}

View File

@ -0,0 +1,125 @@
package com.utopiaindustries.model.ctp;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class StoreItem implements InventoryArtifact {
private long id;
private long itemId;
private String sku;
private String barcode;
private long jobCardId;
@DateTimeFormat( pattern = "yyyy-MM-dd HH:mm:ss" )
private LocalDateTime createdAt;
private String createdBy;
private long finishedItemId;
private long bundleId;
private long accountId;
@Override
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Override
public long getItemId() {
return itemId;
}
public void setItemId(long itemId) {
this.itemId = itemId;
}
@Override
public String getSku() {
return sku;
}
@Override
public String getType() {
return "";
}
public void setSku(String sku) {
this.sku = sku;
}
@Override
public String getBarcode() {
return barcode;
}
public void setBarcode(String barcode) {
this.barcode = barcode;
}
@Override
public long getJobCardId() {
return jobCardId;
}
public void setJobCardId(long jobCardId) {
this.jobCardId = jobCardId;
}
@Override
public LocalDateTime getCreatedAt() {
return createdAt;
}
@Override
public BigDecimal getWrapQuantity() {
return null;
}
@Override
public long getMasterBundleId() {
return 0;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
@Override
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public long getFinishedItemId() {
return finishedItemId;
}
public void setFinishedItemId(long finishedItemId) {
this.finishedItemId = finishedItemId;
}
@Override
public long getBundleId() {
return bundleId;
}
public void setBundleId(long bundleId) {
this.bundleId = bundleId;
}
public long getAccountId() {
return accountId;
}
public void setAccountId(long accountId) {
this.accountId = accountId;
}
}

View File

@ -28,6 +28,12 @@ public class FinishedItemRestController {
@GetMapping("/search-packaging")
public List<FinishedItem> searchFinishedItemsForPackaging(@RequestParam("term") String term,
@RequestParam("is-segregated") boolean isSegregated) {
return finishedItemDAO.findByTermForPackaging(term, true);
return finishedItemDAO.findByTermForPackaging(term, isSegregated, "APPROVED");
}
@GetMapping("/search-store")
public List<FinishedItem> searchFinishedItemsForStore(@RequestParam("term") String term,
@RequestParam("is-segregated") boolean isSegregated) {
return finishedItemDAO.findByTermForPackaging(term, isSegregated,"REJECT");
}
}

View File

@ -50,7 +50,7 @@ public class DashboardService {
//set inventory accounts
List<InventoryAccount> inventoryAccounts = inventoryAccountDAO.findAll();
InventoryAccount inventoryAccount = inventoryAccounts.stream()
.filter(e -> cuttingAccount.equals(e.getTitle())).findFirst().orElse(new InventoryAccount());
.filter(e -> stitchingAccount.equals(e.getTitle())).findFirst().orElse(new InventoryAccount());
Map<String, Integer> inventoryAccountMap = inventoryAccounts.stream()
.filter(e -> cuttingAccount.equals(e.getTitle()) ||
stitchingAccount.equals(e.getTitle()) ||
@ -80,10 +80,10 @@ public class DashboardService {
approvedStitchingOfflineItems = stitchingOfflineItemDAO.findByQCOperationDateAndApproved(startDate1, endDate1, "APPROVED");
qcReject = stitchingOfflineItemDAO.findByQCOperationDateAndIds(startDate1, endDate1, "REJECT", stitchingItemIds);
remaininfQcAlterPieces = stitchingOfflineItemDAO.findByQCOperationDateAndIds(null, forPreviousDate, "REJECT", stitchingItemIds);
approvedStitchingOfflineItemsThenReject = stitchingOfflineItemDAO.findByQCOperationDateAndIds(startDate1, endDate1, "REJECT", stitchingOutIds);
}
if(stitchingOutIds != null && !stitchingOutIds.isEmpty()) {
approvedStitchingOfflineItemsThenReject = stitchingOfflineItemDAO.findByQCOperationDateAndIds(startDate1, endDate1, "REJECT", stitchingOutIds);
}
//set finishing related details
Long alterationPieceFinish = 0L;
Long rejectFinishedItem = 0L;
@ -127,7 +127,7 @@ public class DashboardService {
progress.put("totalWips", (float) stitchingItemIds.size() - qcReject);
progress.put("Alteration", (float) qcReject + approvedStitchingOfflineItemsThenReject);
progress.put("finishing", (float) approved + operationNotPerformed);
progress.put("finishing", (float) approved );
progress.put("ALTER", (float) alterationPieceFinish);
progress.put("Reject", (float) rejectFinishedItem);
progress.put("wash", (float) washFinishedItem);
@ -142,10 +142,11 @@ public class DashboardService {
}
public Map<String, String> getLineDetails(String lineNo) {
String cuttingAccount = "CUTTING ACCOUNT " + lineNo;
String stitchingAccount = "STITCHING ACCOUNT " + lineNo;
List<InventoryAccount> inventoryAccounts = inventoryAccountDAO.findAll();
InventoryAccount inventoryAccount = inventoryAccounts.stream()
.filter(e -> cuttingAccount.equals(e.getTitle())).findFirst().orElse(new InventoryAccount());
.filter(e -> stitchingAccount.equals(e.getTitle())).findFirst().orElse(new InventoryAccount());
int shiftTarget = getTargetShiftWiseOrHourlyWise(inventoryAccount.getShiftMinutes(), inventoryAccount);
int shiftHourlyTarget = getTargetShiftWiseOrHourlyWise(60, inventoryAccount);

View File

@ -19,7 +19,7 @@ import java.util.stream.Collectors;
public class InventoryService {
private final JobCardItemDAO jobCardItemDAO;
private final CutPieceDAO cutPieceDAO;
private final StoreItemDao storeItemDao;
private final BundleDAO bundleDAO;
private final InventoryTransactionLegDAO inventoryTransactionLegDAO;
private final InventoryTransactionDAO inventoryTransactionDAO;
@ -30,9 +30,9 @@ public class InventoryService {
private final StitchingOfflineItemDAO stitchingOfflineItemDAO;
private final PackagingItemsDAO packagingItemsDAO;
public InventoryService(JobCardItemDAO jobCardItemDAO, CutPieceDAO cutPieceDAO, BundleDAO bundleDAO, InventoryTransactionLegDAO inventoryTransactionLegDAO, InventoryTransactionDAO inventoryTransactionDAO, JobCardDAO jobCardDAO, CryptographyService cryptographyService, MasterBundleDAO masterBundleDAO, FinishedItemDAO finishedItemDAO, StitchingOfflineItemDAO stitchingOfflineItemDAO, PackagingItemsDAO packagingItemsDAO) {
public InventoryService(JobCardItemDAO jobCardItemDAO, StoreItemDao storeItemDao, BundleDAO bundleDAO, InventoryTransactionLegDAO inventoryTransactionLegDAO, InventoryTransactionDAO inventoryTransactionDAO, JobCardDAO jobCardDAO, CryptographyService cryptographyService, MasterBundleDAO masterBundleDAO, FinishedItemDAO finishedItemDAO, StitchingOfflineItemDAO stitchingOfflineItemDAO, PackagingItemsDAO packagingItemsDAO) {
this.jobCardItemDAO = jobCardItemDAO;
this.cutPieceDAO = cutPieceDAO;
this.storeItemDao = storeItemDao;
this.bundleDAO = bundleDAO;
this.inventoryTransactionLegDAO = inventoryTransactionLegDAO;
this.inventoryTransactionDAO = inventoryTransactionDAO;
@ -614,6 +614,7 @@ public class InventoryService {
createInventoryTransactionLeg(transaction, finishedItem, lastOutTransaction.getAccountId(), InventoryTransactionLeg.Type.IN.name(), InventoryArtifactType.FINISHED_ITEM.name());
finishedItem.setQaStatus("ALTER");
finishedItem.setIsSegregated(false);
finishedItem.setIsQa(false);
}
}
@ -661,7 +662,7 @@ public class InventoryService {
* Packaging items
* */
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void createPackagingItemAndTransaction(FinishedItemWrapper wrapper) {
public void createPackagingItemAndTransaction(FinishedItemWrapper wrapper, long accountId) {
if (wrapper != null && wrapper.getItems() != null) {
List<FinishedItem> items = wrapper.getItems();
@ -670,9 +671,6 @@ public class InventoryService {
// finished ids
List<Long> finishedItemIds = items.stream().map(FinishedItem::getId)
.collect(Collectors.toList());
// stitched ids
List<Long> stitchedItemIds = items.stream().map(FinishedItem::getStitchedItemId)
.collect(Collectors.toList());
// find parent doc type last IN transaction map
Map<Long, InventoryTransactionLeg> lastFinishedItemIdInTransactionMap = inventoryTransactionLegDAO
@ -696,9 +694,10 @@ public class InventoryService {
if (lastInvTransaction != null) {
// OUT
long fromAccount = lastInvTransaction.getAccountId();
packagingItems1.setAccountId(fromAccount);
createInventoryTransactionLeg(transaction, finishedItem, fromAccount, InventoryTransactionLeg.Type.OUT.name(), InventoryArtifactType.FINISHED_ITEM.name());
// IN
createInventoryTransactionLeg(transaction, packagingItems1, 8, InventoryTransactionLeg.Type.IN.name(), InventoryArtifactType.PACKAGING.name());
createInventoryTransactionLeg(transaction, packagingItems1, accountId, InventoryTransactionLeg.Type.IN.name(), InventoryArtifactType.PACKAGING.name());
}
finishedItem.setIsSegregated(true);
finishedItem.setPackaging(true);
@ -712,6 +711,57 @@ public class InventoryService {
}
}
/*
* store items
* */
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void createStoreItemAndTransaction(FinishedItemWrapper wrapper, long toAccount) {
if (wrapper != null && wrapper.getItems() != null) {
List<FinishedItem> items = wrapper.getItems();
List<FinishedItem> updatedItems = new ArrayList<>();
List<StoreItem> storeItems = new ArrayList<>();
// finished ids
List<Long> finishedItemIds = items.stream().map(FinishedItem::getId)
.collect(Collectors.toList());
// find parent doc type last IN transaction map
Map<Long, InventoryTransactionLeg> lastFinishedItemIdInTransactionMap = inventoryTransactionLegDAO
.findLastTransactionByParentIdAndParentType(InventoryTransactionLeg.Type.IN.name(), finishedItemIds, InventoryArtifactType.FINISHED_ITEM.name())
.stream()
.collect(Collectors.toMap(InventoryTransactionLeg::getParentDocumentId, Function.identity()));
// create transaction
InventoryTransaction transaction = createInventoryTransaction("Against Segregation of Finished Items");
// create IN and OUT for all approved items
for (FinishedItem finishedItem : items) {
InventoryTransactionLeg lastInvTransaction = lastFinishedItemIdInTransactionMap.getOrDefault(finishedItem.getId(), null);
finishedItem.setIsQa(true);
if (finishedItem.getQaStatus().equalsIgnoreCase("REJECT")) {
// create OUT and IN transactions for FI
StoreItem storeItem = (createStoreItems(finishedItem));
storeItem.setId(storeItemDao.save(storeItem));
if (lastInvTransaction != null) {
// OUT
long fromAccount = lastInvTransaction.getAccountId();
storeItem.setAccountId(fromAccount);
createInventoryTransactionLeg(transaction, finishedItem, fromAccount, InventoryTransactionLeg.Type.OUT.name(), InventoryArtifactType.FINISHED_ITEM.name());
// IN
createInventoryTransactionLeg(transaction, storeItem, toAccount, InventoryTransactionLeg.Type.IN.name(), InventoryArtifactType.STORED_ITEM.name());
}
finishedItem.setIsSegregated(false);
finishedItem.setStore(true);
storeItems.add(storeItem);
}
updatedItems.add(finishedItem);
}
// save finish items
finishedItemDAO.saveAll(updatedItems);
storeItemDao.saveAll(storeItems);
}
}
/*
* find item summary by account
@ -737,4 +787,18 @@ public class InventoryService {
packagingItems.setQaStatus(finishedItem.getQaStatus());
return packagingItems;
}
private StoreItem createStoreItems(FinishedItem finishedItem) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
StoreItem storeItem = new StoreItem();
storeItem.setItemId(finishedItem.getItemId());
storeItem.setAccountId(finishedItem.getAccountId());
storeItem.setFinishedItemId(finishedItem.getId());
storeItem.setJobCardId(finishedItem.getJobCardId());
storeItem.setSku(finishedItem.getSku());
storeItem.setBarcode(finishedItem.getBarcode());
storeItem.setCreatedAt(LocalDateTime.now());
storeItem.setCreatedBy(authentication.getName());
return storeItem;
}
}

View File

@ -16,7 +16,7 @@ public class PackagingService {
}
public void createPackagingItem(FinishedItemWrapper wrapper){
inventoryService.createPackagingItemAndTransaction(wrapper);
inventoryService.createPackagingItemAndTransaction(wrapper, wrapper.getAccountId());
}
}

View File

@ -0,0 +1,19 @@
package com.utopiaindustries.service;
import com.utopiaindustries.dao.ctp.PackagingItemsDAO;
import com.utopiaindustries.model.ctp.FinishedItemWrapper;
import org.springframework.stereotype.Service;
@Service
public class StoreService {
private final InventoryService inventoryService;
public StoreService(InventoryService inventoryService, PackagingItemsDAO packagingItemsDAO) {
this.inventoryService = inventoryService;
}
public void createStoreItems(FinishedItemWrapper wrapper ) {
inventoryService.createStoreItemAndTransaction(wrapper, wrapper.getAccountId());
}
}

View File

@ -1,8 +1,8 @@
spring:
uinddatasource:
jdbcUrl: jdbc:mysql://192.168.90.147:3306
username: utopia
password: Utopia01
jdbcUrl: jdbc:mysql://utopia-industries-rr.c5qech8o9lgg.us-east-1.rds.amazonaws.com:3306/inventory
username: cut-to-pack
password: mAzFAivImnTqKJx4KNJ0
driverClassName: com.mysql.cj.jdbc.Driver
logbackUrl: jdbc:mysql://192.168.90.147:3306/uind_logs?serverTimezone=Asia/Karachi
hikari:
@ -19,7 +19,7 @@ spring:
pool-name: UINDCosmosPool
leak-detection-threshold: 2000
localdatasource:
jdbcUrl: jdbc:mysql://192.168.90.147:3306/cut_to_pack
jdbcUrl: jdbc:mysql://localhost:3306/cut_to_pack
username: utopia
password: Utopia01
driverClassName: com.mysql.cj.jdbc.Driver

View File

@ -9,7 +9,7 @@ spring:
minimum-idle: 5
idle-timeout: 30000 # 30 seconds
max-lifetime: 1800000 # 30 minutes
connection-timeout: 30000 # 30 seconds
connection-timeout: 60000 # 30 seconds
leak-detection-threshold: 10000
cosmosdatasource:
jdbcUrl: jdbc:mysql://192.168.90.147:3307

View File

@ -55,6 +55,7 @@
<span v-else-if="item.qaStatus === 'APPROVED'" class="font-lg badge badge-success">{{ item.qaStatus }}</span>
<span v-else-if="item.qaStatus === 'ALTER' || item.qaStatus === 'B GRADE' || item.qaStatus === 'C GRADE' " class="font-lg badge badge-danger">{{ item.qaStatus }}</span>
<span v-else-if="item.qaStatus === 'WASHED'" class="font-lg badge badge-APPROVED">{{ item.qaStatus }}</span>
<span v-else-if="item.qaStatus === 'REJECT'" class="font-lg badge badge-danger">{{ item.qaStatus }}</span>
<td>
<button type="button" class="btn btn-light" v-on:click="removeItem(index)">
<i class="bi bi-trash"></i>

View File

@ -59,7 +59,7 @@
<span v-else-if="item.qaStatus === 'APPROVED'" class="font-lg badge badge-success">{{ item.qaStatus }}</span>
<span v-else-if="item.qaStatus === 'ALTER' || item.qaStatus === 'B GRADE'" class="font-lg badge badge-danger">{{ item.qaStatus }}</span>
<span v-else-if="item.qaStatus === 'WASHED'" class="font-lg badge badge-APPROVED">{{ item.qaStatus }}</span>
<span v-else-if="item.qaStatus === 'REJECT'" class="font-lg badge badge-danger">{{ item.qaStatus }}</span>
</td>
<td>
<button type="button" title="Remove" class="btn btn-light text-left" v-on:click="removeItem(index)">

View File

@ -74,13 +74,15 @@
<a th:href="@{/packaging/}" class="nav-link"
th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/packaging') ? 'active' : ''}">Packaging</a>
</li>
<li class="nav-item" sec:authorize="hasAnyRole('ROLE_STORE', 'ROLE_ADMIN')">
<a th:href="@{/store/}" class="nav-link"
th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/store') ? 'active' : ''}">Store</a>
</li>
<li class="nav-item" sec:authorize="hasAnyRole('ROLE_REPORTING', 'ROLE_ADMIN')">
<a th:href="@{/reporting/summary}" class="nav-link"
<a th:href="@{/reporting/}" class="nav-link"
th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/reporting') ? 'active' : ''}">Reporting</a>
</li>
<li class="nav-item dropdown" sec:authorize="hasRole('ROLE_ADMIN')">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#">Admin</a>
<div class="dropdown-menu">
@ -139,6 +141,17 @@
</li>
</ul>
</nav>
<!-- second level purchase order-->
<nav class="navbar navbar-light bg-light navbar-expand-lg justify-content-between"
th:if="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/purchase-order')}">
<ul class="navbar-nav">
<li class="nav-item"
th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/purchase-order') ? 'active' : ''}">
<a th:href="@{/purchase-order/}" class="nav-link">PO's</a>
</li>
</ul>
</nav>
<!-- second level cutting -->
<nav class="navbar navbar-light bg-light navbar-expand-lg justify-content-between"
th:if="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/cutting')}">
@ -256,6 +269,21 @@
</li>
</ul>
</nav>
<!-- second level store -->
<nav class="navbar navbar-light bg-light navbar-expand-lg justify-content-between"
th:if="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/store')}">
<ul class="navbar-nav">
<li class="nav-item"
th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/store/receive-inventory') ? 'active' : ''}">
<a th:href="@{/store/receive-inventory}" class="nav-link">Receive Inventory</a>
</li>
<li class="nav-item"
th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/store/inventory-accounts') ? 'active' : ''}">
<a th:href="@{/store/inventory-accounts}" class="nav-link">Inventory Accounts</a>
</li>
</ul>
</nav>
</div>
</header>
<!-- table loading skeleton -->

View File

@ -7,26 +7,28 @@
<header class="row page-header" th:replace="_fragments :: page-header"></header>
<main class="row page-main">
<div class="col-sm">
<div th:replace="_notices :: page-notices"></div>
<div class="mb-4 d-flex justify-content-between">
<h3>Receive Finished Items</h3>
<h3>Receive Packing Items</h3>
</div>
<form th:action="'/ctp/packaging/packaging-items'" method="post" id="packagingApp">
<form th:action="'/ctp/packaging/packaging-items'" method="post" id="packagingApp" th:object="${wrapper}">
<div class="bg-light p-3 mb-3">
<div class="form-row">
<div class="col-sm-3 form-group">
<search-item
:is-segregated="true"
url="/ctp/rest/finished-items/search-packaging"
v-on:finished-item-select="onItemSelect">
</search-item>
</div>
<div class="col-sm-3 form-group">
<!-- <label>Packaging Account</label>-->
<!-- <select class="form-control" name="account-id" th:field="*{finishedAccountId}" required>-->
<!-- <option value="">PLease select</option>-->
<!-- <option th:each="account : ${accounts}"-->
<!-- th:value="${account.id}"-->
<!-- th:text="${account.title}"></option>-->
<!-- </select>-->
<label>Packaging Account</label>
<select class="form-control" name="account-id" th:field="*{accountId}" required>
<option value="">PLease select</option>
<option th:each="account : ${accounts}"
th:value="${account.id}"
th:text="${account.title}"></option>
</select>
</div>
</div>
</div>

View File

@ -13,7 +13,7 @@
<div class="col-sm">
<div th:replace="_notices :: page-notices"></div>
<div class="mb-4 d-flex justify-content-between">
<h3>Job Cards</h3>
<h3>All PO's</h3>
<a th:href="@{/purchase-order/new}" class="btn btn-primary">Add New</a>
</div>
<div th:replace="_fragments :: table-loading-skeleton"></div>
@ -55,7 +55,7 @@
</tbody>
</table>
<!-- Show message if purchaseOrder is null or empty -->
<h4 th:if="${purchaseOrder == null or purchaseOrder.isEmpty()}">No cards found.</h4>
<h4 th:if="${purchaseOrder == null or purchaseOrder.isEmpty()}">No POs found.</h4>
</div>
</main>
</div>

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml"
xmlns:uind="http://www.w3.org/1999/xhtml" xmlns:ctp="http://www.w3.org/1999/xhtml">
<head th:replace="_fragments :: head('Finished Item')"></head>
<head th:replace="_fragments :: head('Qc Finished Item')"></head>
<body>
<div class="container-fluid">
<header class="row page-header" th:replace="_fragments :: page-header"></header>

View File

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml"
xmlns:uind="http://www.w3.org/1999/xhtml" xmlns:ctp="http://www.w3.org/1999/xhtml">
<head th:replace="_fragments :: head('Store Inventory Accounts')"></head>
<body>
<div class="container-fluid">
<header class="row page-header" th:replace="_fragments :: page-header"></header>
<main class="row page-main">
<!-- sidebar starts -->
<aside class="col-sm-2" th:replace="/cutting/_cutting-inventory-account-sidebar :: sidebar"></aside>
<!-- sidebar ends -->
<!--header starts-->
<div class="col-sm">
<div th:replace="_notices :: page-notices"></div>
<div class="mb-4 d-flex justify-content-between">
<h3>Store Inventory Accounts</h3>
</div>
<div th:replace="_fragments :: table-loading-skeleton"></div>
<table class="table table-striped" data-account-table data-order="[[ 0, &quot;asc&quot; ]]">
<thead>
<tr>
<th></th>
<th></th>
<th>ID</th>
<th>Title</th>
<th>Parent Type</th>
<th>Active</th>
<th>Created By</th>
<th>Created At</th>
<th>Location</th>
<th>Note</th>
</tr>
</thead>
<tbody>
<tr th:each="account : ${accounts}" th:object="${account}">
<td data-show-dropdown-transactions
th:data-account-id="${account.id}" title="Transactions">
<span data-dropdown-icon-transactions class="bi bi-caret-right-fill"></span>
</td>
<td data-show-dropdown-summary
th:data-account-id="${account.id}" title="Summary">
<span data-dropdown-icon-summary class="bi bi-caret-right"></span>
</td>
<td th:text="*{id}"></td>
<td th:text="*{title}"></td>
<td th:text="*{parentEntityType}"></td>
<td>
<span class="badge badge-ACTIVE" th:if="*{active}">ACTIVE</span>
<span class="badge badge-danger" th:unless="*{active}" >INACTIVE</span>
</td>
<td th:text="*{createdBy}"></td>
<td ctp:formatdatetime="*{createdAt}"></td>
<td >
<th:block th:switch="*{locationSiteId}">
<span th:each="location: ${locations}" th:case="${location.id}" th:text="${location.title}"></span>
</th:block>
</td>
<td class="font-italic" th:text="*{notes}"></td>
</tr>
</tbody>
</table>
</div>
</main>
</div>
<div th:replace="_fragments :: page-footer-scripts"></div>
<script>
const $body = $( 'body' );
// custom data table config
const dataTableConfig = {
pageLength: 100,
searching: true,
lengthChange: false,
processing: false,
dom: `
<'row'<'col-sm-5'B><'col-sm-7'f>>
<'row'<'col-sm-12't>>
<'row'<'col-sm-5'i><'col-sm-7'p>>`,
buttons: [{
extend: 'excel',
text: '',
className: 'bi bi-file-earmark-spreadsheet btn-sm'
}]
}
const accTable = $( '[data-account-table]' ).DataTable( dataTableConfig );
// handle click on dropdown
$body.on( 'click', '[data-show-dropdown-transactions]', function( e ) {
e.preventDefault();
const $this = $( this );
const $tr = $this.closest( 'tr' );
const $row = accTable.row( $tr );
const $spanDropdown = $tr.find( '[data-dropdown-icon-transactions]' );
const accountId= $this.data( 'account-id' );
$spanDropdown.toggleClass( 'bi-caret-right-fill bi-caret-down-fill' );
if( $row.child.isShown() ){
$row.child.hide();
}
else {
$row.child(`<span class="spinner-border text-center spinner-border-md" role="status"></span>`).show();
$.ajax({
url: `/ctp/inventory-transactions?account-id=${accountId}`,
success: function( data ){
// show fetched page
$row.child( data ).show();
}
});
}
});
$body.on( 'click', '[data-show-dropdown-summary]', function( e ) {
e.preventDefault();
const $this = $( this );
const $tr = $this.closest( 'tr' );
const $row = accTable.row( $tr );
const $spanDropdown = $tr.find( '[data-dropdown-icon-summary]' );
const accountId= $this.data( 'account-id' );
$spanDropdown.toggleClass( 'bi-caret-right bi-caret-down' );
if( $row.child.isShown() ){
$row.child.hide();
}
else {
$row.child(`<span class="spinner-border text-center spinner-border-md" role="status"></span>`).show();
$.ajax({
url: `/ctp/inventory-summary?account-id=${accountId}`,
success: function( data ){
// show fetched page
$row.child( data ).show();
}
});
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml"
xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head th:replace="_fragments :: head('Store Receive Inventory')"></head>
<body>
<div class="container-fluid">
<header class="row page-header" th:replace="_fragments :: page-header"></header>
<main class="row page-main">
<div class="col-sm">
<div th:replace="_notices :: page-notices"></div>
<div class="mb-4 d-flex justify-content-between">
<h3>Receive Rejected Items</h3>
</div>
<form th:action="'/ctp/store/store-items'" method="post" id="packagingApp" th:object="${wrapper}">
<div class="bg-light p-3 mb-3">
<div class="form-row">
<div class="col-sm-3 form-group">
<search-item
:is-segregated="false"
url="/ctp/rest/finished-items/search-store"
v-on:finished-item-select="onItemSelect">
</search-item>
</div>
<div class="col-sm-3 form-group">
<label>Store Account</label>
<select class="form-control" name="accountId" th:field="*{accountId}" required>
<option value="">PLease select</option>
<option th:each="account : ${accounts}"
th:value="${account.id}"
th:text="${account.title}"></option>
</select>
</div>
</div>
</div>
<div class="bg-light p-3 mb-3">
<h6 class="mb-3">Search Finished Items</h6>
<finish-item-table
v-bind:items="items"
v-on:remove-item="removeItem"
></finish-item-table>
</div>
<div class="alert alert-danger" v-if="hasDuplicates()">Duplicate Item Selected</div>
<button class="btn btn-primary" type="submit" v-bind:disabled="hasDuplicates()">Submit</button>
<a th:href="@{/packaging/receive-inventory}" class="btn btn-light">Cancel</a>
</form>
<script th:inline="javascript">
window.ctp.accounts = [[${accounts}]];
</script>
<script th:src="@{/js/vendor/compressor.min.js}"></script>
<script th:src="@{/js/packaging/packaging-item-form.js}"></script>
</div>
</main>
</div>
<div th:replace="_fragments :: page-footer-scripts"></div>
</body>
</html>