Merge pull request 'feature/auto-create-wip-on-bundle-receive' (#40) from feature/auto-create-wip-on-bundle-receive into main

Reviewed-on: #40
pull/41/head
usama.jameel 2025-07-25 04:13:46 +00:00
commit 0ca1c26785
11 changed files with 119 additions and 65 deletions

View File

@ -3,9 +3,9 @@ package com.utopiaindustries.controller;
import com.utopiaindustries.auth.CuttingRole;
import com.utopiaindustries.dao.ctp.BundleWrapper;
import com.utopiaindustries.model.ctp.JobCardWrapper;
import com.utopiaindustries.model.ctp.StitchingOfflineItem;
import com.utopiaindustries.service.*;
import com.utopiaindustries.util.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@ -13,6 +13,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
@Controller
@CuttingRole
@ -184,4 +185,41 @@ public class CuttingController {
return "redirect:/cutting/master-bundles";
}
}
@PostMapping( "/generate-qrCode" )
public Object generateBarcode(@RequestParam( name = "ids" ,required = false) Long[] ids,
@RequestParam( name = "artifactType" ) String artifactType, RedirectAttributes redirectAttributes ) throws Exception {
if (ids == null){
redirectAttributes.addFlashAttribute( "error", "Select At least One CheckBox" );
return "redirect:/cutting/cutting-items"; }
try {
barcodeService.generateBarcodes( Arrays.asList( ids ), artifactType );
redirectAttributes.addFlashAttribute( "success", "Barcode generated successfully" );
return "redirect:/cutting/cutting-items";
}catch (Exception e){
redirectAttributes.addFlashAttribute( "error", e );
return "redirect:/cutting/cutting-items"; }
}
/*
* get finished items
* */
@GetMapping( "/cutting-items" )
public String getStitchingOfflineItems( @RequestParam(value = "id", required = false ) String id,
@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,
@RequestParam(value = "bundle-id", required = false) Long bundleId,
@RequestParam( value = "count", required = false, defaultValue = "100") Long count,
@RequestParam( value = "status", required = false) String status,
Model model, RedirectAttributes redirect){
LocalDate startDate1 = StringUtils.isNullOrEmpty(startDate) ? LocalDate.now().minusDays(30) : LocalDate.parse(startDate);
LocalDate endDate1 = StringUtils.isNullOrEmpty(endDate) ? LocalDate.now() : LocalDate.parse(endDate);
List<StitchingOfflineItem> itemList = bundleService.getStitchedOfflineItems( id, itemId, sku, status, startDate, endDate, bundleId, count );
model.addAttribute("items", itemList ) ;
model.addAttribute("startDate", startDate1);
model.addAttribute("endDate", endDate1);
return "/stitching/stitched-offline-items";
}
}

View File

@ -116,7 +116,7 @@ public class StitchingController {
RedirectAttributes redirectAttributes,
Model model ){
try {
inventoryService.createStitchingOfflineItemsFromJobCard( bundleWrapper );
// inventoryService.createStitchingOfflineItemsFromJobCard( bundleWrapper );
redirectAttributes.addFlashAttribute("success", "Stitch Items Created Successfully");
} catch ( Exception exception ){
exception.printStackTrace();
@ -124,21 +124,4 @@ public class StitchingController {
}
return "redirect:/stitching/create-stitching-items";
}
@PostMapping( "/generate-barcodes" )
public Object generateBarcode(@RequestParam( name = "ids" ,required = false) Long[] ids,
@RequestParam( name = "artifactType" ) String artifactType, RedirectAttributes redirectAttributes ) throws Exception {
if (ids == null){
redirectAttributes.addFlashAttribute( "error", "Select At least One CheckBox" );
return "redirect:/stitching/stitching-offline-items";
}
try {
barcodeService.generateBarcodes( Arrays.asList( ids ), artifactType );
redirectAttributes.addFlashAttribute( "success", "Barcode generated successfully" );
return "redirect:/stitching/stitching-offline-items";
}catch (Exception e){
redirectAttributes.addFlashAttribute( "error", e );
return "redirect:/stitching/stitching-offline-items";
}
}
}

View File

@ -24,12 +24,12 @@ public class StitchingOfflineItemDAO {
private final String SELECT_ALL_QUERY = String.format( "SELECT * FROM %s ORDER BY id DESC", 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, qa_remarks, qa_status,bundle_id, qc_done_at) VALUES (:id, :item_id, :sku, :barcode, :created_at, :created_by, :job_card_id, :is_qa, :qa_remarks, :qa_status, :bundle_id, :qc_done_at) 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), qa_remarks = VALUES(qa_remarks), qa_status = VALUES(qa_status), bundle_id = VALUES(bundle_id), qc_done_at = VALUES(qc_done_at)", 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, qa_remarks, qa_status,bundle_id, qc_done_at, inline_received) VALUES (:id, :item_id, :sku, :barcode, :created_at, :created_by, :job_card_id, :is_qa, :qa_remarks, :qa_status, :bundle_id, :qc_done_at, :inline_received) 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), qa_remarks = VALUES(qa_remarks), qa_status = VALUES(qa_status), bundle_id = VALUES(bundle_id), qc_done_at = VALUES(qc_done_at), inline_received = VALUES(inline_received)", TABLE_NAME );
private final String SELECT_BY_LIMIT = String.format("SELECT * FROM %s ORDER BY id DESC LIMIT :limit", 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_IDS = String.format( "SELECT * FROM %s WHERE id IN (:ids)", TABLE_NAME );
private final String SELECT_BY_TERM = String.format( "SELECT * FROM %s WHERE barcode LIKE :term ORDER BY ID DESC", TABLE_NAME );
private final String SELECT_BY_MASTER_ID = String.format( "SELECT * FROM %s WHERE job_card_id = :job_card_id", TABLE_NAME );
private final String SELECT_BY_TERM = String.format( "SELECT * FROM %s WHERE barcode LIKE :term AND inline_received = TRUE ORDER BY ID DESC", TABLE_NAME );
private final String SELECT_BY_BUNDLE_ID = String.format( "SELECT * FROM %s WHERE bundle_id = :bundle_id", TABLE_NAME );
private final String COUNT_TOTAL_QA_ITEMS= String.format("SELECT COUNT(*) FROM %s WHERE job_card_id = :job_card_id AND is_qa IS TRUE",TABLE_NAME);
private final String SELECT_BY_TIME_AND_CARD_ID= String.format("SELECT * FROM %s WHERE job_card_id = :job_card_id ORDER BY created_at DESC LIMIT 1;",TABLE_NAME);
private final String SELECT_BY_JOB_CARD_AND_DATE = String.format( "SELECT * FROM %s WHERE job_card_id = :job_card_id AND (:start_date IS NULL OR :end_date IS NULL OR created_at BETWEEN :start_date AND :end_date)", TABLE_NAME );
@ -54,7 +54,8 @@ public class StitchingOfflineItemDAO {
.addValue("is_qa", stitchingOfflineItem.getIsQa() )
.addValue("qa_remarks", stitchingOfflineItem.getQaRemarks() )
.addValue("qc_done_at", stitchingOfflineItem.getQcDoneAt() )
.addValue("qa_status", stitchingOfflineItem.getQaStatus() );
.addValue("qa_status", stitchingOfflineItem.getQaStatus() )
.addValue("inline_received", stitchingOfflineItem.isInlineReceived() );
return params;
}
@ -128,10 +129,10 @@ public class StitchingOfflineItemDAO {
return namedParameterJdbcTemplate.query( SELECT_BY_TERM , params, new StitchingOfflineItemRowMapper() );
}
public List<StitchingOfflineItem> findByMasterId( long masterId ){
public List<StitchingOfflineItem> findByBundleId( long bundleId ){
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("job_card_id", masterId );
return namedParameterJdbcTemplate.query( SELECT_BY_MASTER_ID , params, new StitchingOfflineItemRowMapper() );
params.addValue("bundle_id", bundleId );
return namedParameterJdbcTemplate.query( SELECT_BY_BUNDLE_ID , params, new StitchingOfflineItemRowMapper() );
}
public HashMap<Long, Long> findTotalStitchingOfflineItems(List<Long> itemIds, long jobCardId) {

View File

@ -25,6 +25,7 @@ public class StitchingOfflineItemRowMapper implements RowMapper<StitchingOffline
stitchingOfflineItem.setCreatedBy( rs.getString( "created_by" ) );
stitchingOfflineItem.setJobCardId( rs.getLong("job_card_id") );
stitchingOfflineItem.setIsQa( rs.getBoolean("is_qa"));
stitchingOfflineItem.setInlineReceived( rs.getBoolean("inline_received"));
stitchingOfflineItem.setQaStatus( rs.getString("qa_status" ) );
stitchingOfflineItem.setQaRemarks( rs.getString("qa_remarks") );
return stitchingOfflineItem;

View File

@ -23,6 +23,8 @@ public class StitchingOfflineItem implements InventoryArtifact {
private String qaRemarks;
private String qaStatus;
private long bundleId;
private boolean inlineReceived;
@Override
public String getType() {
@ -137,6 +139,14 @@ public class StitchingOfflineItem implements InventoryArtifact {
this.qcDoneAt = qcDoneAt;
}
public boolean isInlineReceived() {
return inlineReceived;
}
public void setInlineReceived(boolean inlineReceived) {
this.inlineReceived = inlineReceived;
}
@Override
public String toString() {
return "StitchingOfflineItem{" +

View File

@ -3,6 +3,8 @@ package com.utopiaindustries.querybuilder;
import com.utopiaindustries.util.MySQLUtils;
import com.utopiaindustries.util.StringUtils;
import java.util.List;
public class QueryBuilder {
private StringBuilder query;
private String table;

View File

@ -29,13 +29,17 @@ public class BundleService {
private final CryptographyService cryptographyService;
private final FinishedItemDAO finishedItemDAO;
private final StitchingOfflineItemDAO stitchingOfflineItemDAO;
private final InventoryService inventoryService;
private final InventoryTransactionLegDAO inventoryTransactionLegDAO;
public BundleService(BundleDAO bundleDAO, MasterBundleDAO masterBundleDAO, CryptographyService cryptographyService, FinishedItemDAO finishedItemDAO, StitchingOfflineItemDAO stitchingOfflineItemDAO) {
public BundleService(BundleDAO bundleDAO, MasterBundleDAO masterBundleDAO, CryptographyService cryptographyService, FinishedItemDAO finishedItemDAO, StitchingOfflineItemDAO stitchingOfflineItemDAO, InventoryService inventoryService, InventoryTransactionLegDAO inventoryTransactionLegDAO) {
this.bundleDAO = bundleDAO;
this.masterBundleDAO = masterBundleDAO;
this.cryptographyService = cryptographyService;
this.finishedItemDAO = finishedItemDAO;
this.stitchingOfflineItemDAO = stitchingOfflineItemDAO;
this.inventoryService = inventoryService;
this.inventoryTransactionLegDAO = inventoryTransactionLegDAO;
}
/*
@ -169,6 +173,7 @@ public class BundleService {
List<Bundle> newBundles = new ArrayList<>();
for( Bundle bundle : wrapper.getBundles() ){
bundle.setMasterBundleId( id );
bundle.setCurrentProduction(bundle.getWrapQuantity());
newBundles.add( bundle );
// sku and item in master bundle
if( StringUtils.isNullOrEmpty( sku ) ){
@ -188,6 +193,8 @@ public class BundleService {
masterBundleDAO.save( masterBundle );
//save child bundles
bundleDAO.saveAll( newBundles );
inventoryService.createStitchingOfflineItemsFromBundle(newBundles);
return id;
}
return 0;

View File

@ -77,11 +77,11 @@ public class DashboardService {
Long approvedStitchingOfflineItemsThenReject = 0L;
long qcReject = 0L;
if (stitchingItemIds != null && !stitchingItemIds.isEmpty()) {
approvedStitchingOfflineItems = stitchingOfflineItemDAO.findByQCOperationDateAndApproved(startDate1, endDate1, "APPROVED");
qcReject = stitchingOfflineItemDAO.findByQCOperationDateAndIds(startDate1, endDate1, "REJECT", stitchingItemIds);
remaininfQcAlterPieces = stitchingOfflineItemDAO.findByQCOperationDateAndIds(null, forPreviousDate, "REJECT", stitchingItemIds);
}
if(stitchingOutIds != null && !stitchingOutIds.isEmpty()) {
approvedStitchingOfflineItems = stitchingOfflineItemDAO.findByQCOperationDateAndIds(startDate1, endDate1, "APPROVED", stitchingOutIds);
approvedStitchingOfflineItemsThenReject = stitchingOfflineItemDAO.findByQCOperationDateAndIds(startDate1, endDate1, "REJECT", stitchingOutIds);
}
//set finishing related details

View File

@ -285,6 +285,11 @@ public class InventoryService {
masterBundle.setIsReceived(true);
masterBundle.setAccountId(toAccount);
masterBundleDAO.save(masterBundle);
//create transaction of offline items And items already create when master bundle create
BundleWrapper bundleWrapper = new BundleWrapper();
bundleWrapper.setBundles(bundles);
createStitchingOfflineItemsTransactionWhenReceivedMasterBundle(bundleWrapper);
}
}
@ -329,51 +334,29 @@ public class InventoryService {
}
/*
* create finished items from master bundles
* create stitch items from master bundles
* */
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void createStitchingOfflineItemsFromJobCard(BundleWrapper wrapper) {
public void createStitchingOfflineItemsFromBundle(List<Bundle> bundles) {
List<JobCardItem> updatedItems = new ArrayList<>();
List<Bundle> updateBundles = new ArrayList<>();
JobCard jobCard = null;
//switching table transaction in Transaction Table
InventoryTransaction transaction = createInventoryTransaction("Against Movement from Stitching to Stitched Offline Item");
inventoryTransactionDAO.save(transaction);
//TransactionLeg Transaction
List<Long> bundleIds = wrapper.getBundles().stream().map(Bundle::getId).collect(Collectors.toList());
Map<Long, InventoryTransactionLeg> lastBundleIdInTransactionMap = inventoryTransactionLegDAO
.findLastTransactionByParentIdAndParentType(InventoryTransactionLeg.Type.IN.name(), bundleIds, InventoryArtifactType.STITCH_BUNDLE.name())
.stream()
.collect(Collectors.toMap(InventoryTransactionLeg::getParentDocumentId, Function.identity()));
for (Bundle subBundle : wrapper.getBundles()) {
long accountId = masterBundleDAO.find(subBundle.getMasterBundleId()).getAccountId();
for (Bundle subBundle : bundles) {
if (subBundle.getCurrentProduction() != null && subBundle.getCurrentProduction().compareTo(BigDecimal.ZERO) != 0) {
Bundle bundle = bundleDAO.find(subBundle.getId());
jobCard = jobCardDAO.find(subBundle.getJobCardId());
long production = (bundle.getProduction() == null) ? 0 : bundle.getProduction().longValue();
long wrapQuantity = bundle.getWrapQuantity().longValue();
JobCardItem jobCardItem = jobCardItemDAO.findByCardIdAndItemId(subBundle.getJobCardId(), subBundle.getItemId());
BigDecimal previousTotalProduction = jobCardItem.getTotalProduction() == null ? BigDecimal.ZERO : jobCardItem.getTotalProduction();
InventoryTransactionLeg lastInvTransaction = lastBundleIdInTransactionMap.getOrDefault(subBundle.getId(), null);
if (lastInvTransaction != null) {
if (wrapQuantity == production + subBundle.getCurrentProduction().longValue()) {
// OUT
long fromAccount = lastInvTransaction.getAccountId();
createInventoryTransactionLeg(transaction, subBundle, accountId, InventoryTransactionLeg.Type.OUT.name(), InventoryArtifactType.STITCH_BUNDLE.name());
}
}
// create stitchingOfflineItems items
List<StitchingOfflineItem> stitchingOfflineItems = createStitchingOfflineItems(subBundle.getCurrentProduction(), jobCardItem, subBundle.getId());
// create IN Transactions of Finished Items into account
createTransactions(stitchingOfflineItems, accountId, InventoryArtifactType.STITCHING_OFFLINE.name());
createStitchingOfflineItems(subBundle.getCurrentProduction(), jobCardItem, subBundle.getId());
//update job card item and then add in list
jobCardItem.setTotalProduction(previousTotalProduction.add(subBundle.getCurrentProduction()));
jobCardItem.setJobCardId(jobCard.getId());
updatedItems.add(jobCardItem);
// update bundle production
BigDecimal pro = BigDecimal.valueOf(production + subBundle.getCurrentProduction().longValue());
bundle.setProduction(pro);
bundleDAO.save(bundle);
@ -382,6 +365,34 @@ public class InventoryService {
jobCardItemDAO.saveAll(updatedItems);
}
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void createStitchingOfflineItemsTransactionWhenReceivedMasterBundle(BundleWrapper wrapper) {
//switching table transaction in Transaction Table
InventoryTransaction transaction = createInventoryTransaction("Against Movement from Stitching to Stitched Offline Item");
inventoryTransactionDAO.save(transaction);
//TransactionLeg Transaction
List<Long> bundleIds = wrapper.getBundles().stream().map(Bundle::getId).collect(Collectors.toList());
for (Bundle subBundle : wrapper.getBundles()) {
long accountId = masterBundleDAO.find(subBundle.getMasterBundleId()).getAccountId();
// OUT
createInventoryTransactionLeg(transaction, subBundle, accountId, InventoryTransactionLeg.Type.OUT.name(), InventoryArtifactType.STITCH_BUNDLE.name());
// find stitchingOfflineItems items
List<StitchingOfflineItem> stitchingOfflineItems = stitchingOfflineItemDAO.findByBundleId(subBundle.getId());
// create IN Transactions of Finished Items into account
createTransactions(stitchingOfflineItems, accountId, InventoryArtifactType.STITCHING_OFFLINE.name());
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
List<StitchingOfflineItem> updatedItems = stitchingOfflineItems.stream().map(e-> {
e.setInlineReceived(true);
e.setCreatedBy(authentication.getName());
return e;
}).collect(Collectors.toList());
stitchingOfflineItemDAO.saveAll(updatedItems);
}
}
/*
* create finished items
* */

View File

@ -181,6 +181,11 @@
th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/cutting/master-bundles') ? 'active' : ''}">
<a th:href="@{/cutting/master-bundles}" class="nav-link">Master Bundles</a>
</li>
<li class="nav-item"
th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/cutting/cutting-items') ? 'active' : ''}">
<a th:href="@{/cutting/cutting-items}" class="nav-link">Generate Stitching QR</a>
</li>
</ul>
</nav>
<!--Second level of reporting-->

View File

@ -12,12 +12,8 @@
<!--header starts-->
<div class="col-sm">
<div th:replace="_notices :: page-notices"></div>
<div class="mb-4 d-flex justify-content-between">
<h3>Stitching WIP's</h3>
<a th:href="@{/stitching/create-stitching-items}" class="btn btn-primary">Create Stitching WIP's</a>
</div>
<div th:replace="_fragments :: table-loading-skeleton"></div>
<form th:action="@{/stitching/generate-barcodes}" method="post">
<form th:action="@{/cutting/generate-qrCode}" method="post">
<input hidden="hidden" name="artifactType" value="FinishedItem">
<table th:if="${ #lists != null && #lists.size(items) != 0 }" class="table table-striped table-bordered" data-table
data-order="[[ 0, &quot;desc&quot; ]]">
@ -30,7 +26,7 @@
<th>Created At</th>
<th>Created By</th>
<th>Is QA</th>
<th>
<th th:if="${!#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/stitching/stitching-offline-items')}">
<div class="mb-2" >
<button class="btn btn-sm btn-outline-primary" type="submit">Generate QR code</button>
</div>
@ -54,7 +50,7 @@
<div th:text="*{qaStatus}"></div>
<div th:text="*{qaRemarks}"></div>
</td>
<td>
<td th:if="${!#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/stitching/stitching-offline-items')}">
<div class="form-group form-check mb-0">
<input class="form-check-input" type="checkbox"
th:value="*{id}"