add purchase for cut to pack locally

pull/23/head
Usama Khan 2025-05-13 05:24:46 +05:00
parent 4d235b2975
commit 88cfa11682
16 changed files with 462 additions and 197 deletions

View File

@ -2,13 +2,13 @@ package com.utopiaindustries.controller;
import com.utopiaindustries.auth.PurchaseOrderCTPRole; import com.utopiaindustries.auth.PurchaseOrderCTPRole;
import com.utopiaindustries.model.ctp.JobCard; import com.utopiaindustries.model.ctp.JobCard;
import com.utopiaindustries.model.ctp.PurchaseOrderCTP;
import com.utopiaindustries.service.PurchaseOrderCTPService; import com.utopiaindustries.service.PurchaseOrderCTPService;
import com.utopiaindustries.util.StringUtils; import com.utopiaindustries.util.StringUtils;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.bind.annotation.RequestParam;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
@ -37,7 +37,7 @@ public class PurchaseOrderCTPController {
model.addAttribute("purchaseOrder", purchaseOrderCTPService.getAllPurchaseOrderCtp(purchaseOrderCode, articleName, startDate.toString(), endDate.toString(), limit) ); model.addAttribute("purchaseOrder", purchaseOrderCTPService.getAllPurchaseOrderCtp(purchaseOrderCode, articleName, startDate.toString(), endDate.toString(), limit) );
model.addAttribute("startDate", startDate); model.addAttribute("startDate", startDate);
model.addAttribute("endDate", endDate); model.addAttribute("endDate", endDate);
return "job-card-list"; return "/purchaseOrder/purchase-order-list";
} }
@GetMapping( "/new" ) @GetMapping( "/new" )
@ -45,4 +45,46 @@ public class PurchaseOrderCTPController {
model.addAttribute("purchaseOrder", purchaseOrderCTPService.createNewPurchaseOrderCTP() ); model.addAttribute("purchaseOrder", purchaseOrderCTPService.createNewPurchaseOrderCTP() );
return "/purchaseOrder/purchase-order-form"; return "/purchaseOrder/purchase-order-form";
} }
@GetMapping( value = "/edit/{id}" )
public String showJobCardEditForm( @PathVariable("id") long id,
Model model ){
model.addAttribute("purchaseOrder", purchaseOrderCTPService.searchPurchaseOrderById( id ) );
return "/purchaseOrder/purchase-order-form";
}
/*
* draft
* */
@PostMapping( value ="/edit" , params = "user=draft" )
public String saveJobCard( @ModelAttribute PurchaseOrderCTP purchaseOrderCTP,
RedirectAttributes redirectAttributes,
Model model ){
try {
purchaseOrderCTP.setStatus( PurchaseOrderCTP.Status.DRAFT.name() );
purchaseOrderCTPService.save( purchaseOrderCTP );
redirectAttributes.addFlashAttribute("success", "Successfully saved!" );
} catch ( Exception ex ){
redirectAttributes.addFlashAttribute("error", ex.getMessage() );
}
return "redirect:/purchase-order";
}
@PostMapping( value ="/edit" , params = "user=post" )
public String postJobCard( @ModelAttribute PurchaseOrderCTP purchaseOrderCTP,
RedirectAttributes redirectAttributes,
Model model ){
try {
purchaseOrderCTP.setStatus( PurchaseOrderCTP.Status.POSTED.name() );
purchaseOrderCTPService.save( purchaseOrderCTP );
redirectAttributes.addFlashAttribute("success", "Successfully saved!" );
} catch ( Exception ex ){
redirectAttributes.addFlashAttribute("error", ex.getMessage() );
}
return "redirect:/purchase-order";
}
} }

View File

@ -1,7 +1,9 @@
package com.utopiaindustries.dao.ctp; package com.utopiaindustries.dao.ctp;
import com.utopiaindustries.dao.uind.PurchaseOrderRowMapper;
import com.utopiaindustries.model.ctp.JobCard; import com.utopiaindustries.model.ctp.JobCard;
import com.utopiaindustries.model.ctp.PurchaseOrderCTP; import com.utopiaindustries.model.ctp.PurchaseOrderCTP;
import com.utopiaindustries.model.uind.PurchaseOrder;
import com.utopiaindustries.util.KeyHolderFunctions; import com.utopiaindustries.util.KeyHolderFunctions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
@ -36,6 +38,7 @@ public class PurchaseOrderCTPDao {
"status = VALUES(status)", "status = VALUES(status)",
TABLE_NAME); TABLE_NAME);
private final String SELECT_BY_LIMIT = String.format( "SELECT * FROM %s WHERE created_by = :created_by ORDER BY id ASC limit :limit", TABLE_NAME ); private final String SELECT_BY_LIMIT = String.format( "SELECT * FROM %s WHERE created_by = :created_by ORDER BY id ASC limit :limit", TABLE_NAME );
private final String SELECT_BY_TERM = String.format( "SELECT * FROM %s WHERE purchase_order_code LIKE :term limit 100 offset 0", TABLE_NAME );
// prepare query params // prepare query params
@ -109,4 +112,13 @@ public class PurchaseOrderCTPDao {
params.addValue("limit", limit.intValue()); params.addValue("limit", limit.intValue());
return namedParameterJdbcTemplate.query( SELECT_ALL_QUERY_WITH_LIMIT, params, new PurchaseOrderCTPRowMapper() ); return namedParameterJdbcTemplate.query( SELECT_ALL_QUERY_WITH_LIMIT, params, new PurchaseOrderCTPRowMapper() );
} }
/*
* find by term
* */
public List<PurchaseOrderCTP> findByTerm(String term ){
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("term", "%" + term + "%" );
return namedParameterJdbcTemplate.query( SELECT_BY_TERM, params, new PurchaseOrderCTPRowMapper() );
}
} }

View File

@ -9,5 +9,6 @@ public enum Roles {
ROLE_QUALITY_CONTROL, ROLE_QUALITY_CONTROL,
ROLE_FINISHING, ROLE_FINISHING,
ROLE_PACKAGING, ROLE_PACKAGING,
ROLE_REPORTING ROLE_REPORTING,
ROLE_PURCHASE_ORDER
} }

View File

@ -1,6 +1,8 @@
package com.utopiaindustries.restcontroller; package com.utopiaindustries.restcontroller;
import com.utopiaindustries.model.ctp.PurchaseOrderCTP;
import com.utopiaindustries.model.uind.PurchaseOrder; import com.utopiaindustries.model.uind.PurchaseOrder;
import com.utopiaindustries.service.PurchaseOrderCTPService;
import com.utopiaindustries.service.PurchaseOrderService; import com.utopiaindustries.service.PurchaseOrderService;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -14,9 +16,11 @@ import java.util.List;
public class PurchaseOrderRestController { public class PurchaseOrderRestController {
private final PurchaseOrderService purchaseOrderService; private final PurchaseOrderService purchaseOrderService;
private final PurchaseOrderCTPService purchaseOrderCTPService;
public PurchaseOrderRestController(PurchaseOrderService purchaseOrderService) { public PurchaseOrderRestController(PurchaseOrderService purchaseOrderService, PurchaseOrderCTPService purchaseOrderCTPService) {
this.purchaseOrderService = purchaseOrderService; this.purchaseOrderService = purchaseOrderService;
this.purchaseOrderCTPService = purchaseOrderCTPService;
} }
/* /*
@ -26,4 +30,12 @@ public class PurchaseOrderRestController {
public List<PurchaseOrder> findByTerm(@RequestParam("term") String term ) { public List<PurchaseOrder> findByTerm(@RequestParam("term") String term ) {
return purchaseOrderService.findByTerm( term ); return purchaseOrderService.findByTerm( term );
} }
/*
* search by term in ctp purchase order table
* */
@GetMapping( "/ctp-po-search" )
public List<PurchaseOrderCTP> findByTermInCtpPurchaseOrderTable(@RequestParam("term") String term ) {
return purchaseOrderCTPService.findByTerm( term );
}
} }

View File

@ -71,11 +71,7 @@ public class InventoryAccountService {
} }
public List<InventoryAccount> findInventoryAccounts(){ public List<InventoryAccount> findInventoryAccounts(){
List<InventoryAccount> accounts = inventoryAccountDAO.findAll(); return inventoryAccountDAO.findAll();
for( InventoryAccount account : accounts ){
account.setLocationTitle( locationSiteDAO.find( account.getLocationSiteId() ).getTitle() );
}
return accounts;
} }
public List<InventoryAccount> findInventoryAccountsByFilter(String id, String title, String active, String createdBy, String startDate, String endDate, public List<InventoryAccount> findInventoryAccountsByFilter(String id, String title, String active, String createdBy, String startDate, String endDate,

View File

@ -149,20 +149,7 @@ public class JobCardService {
for (JobCardItem item : jobCard.getItems()) { for (JobCardItem item : jobCard.getItems()) {
item.setJobCardId(jobCardId); item.setJobCardId(jobCardId);
long itemId = jobCardItemDAO.save(item); long itemId = jobCardItemDAO.save(item);
for (CutPiece cutPiece : item.getCutPieces()) {
cutPiece.setJobCardItemId(itemId);
if (!skuCutPiecesDAO.doesExist(cutPiece.getType(), item.getSku())){
SkuCutPieces skuCutPieces = new SkuCutPieces();
skuCutPieces.setType(cutPiece.getType());
skuCutPieces.setSku(item.getSku());
//save cut-piece for sku next time fetch
skuCutPiecesDAO.save(skuCutPieces);
}
cutPieces.add(cutPiece);
}
} }
// save all pieces
cutPieceDAO.saveAll(cutPieces);
} }
} }

View File

@ -2,6 +2,7 @@ package com.utopiaindustries.service;
import com.utopiaindustries.dao.ctp.PurchaseOrderCTPDao; import com.utopiaindustries.dao.ctp.PurchaseOrderCTPDao;
import com.utopiaindustries.model.ctp.*; import com.utopiaindustries.model.ctp.*;
import com.utopiaindustries.model.uind.PurchaseOrder;
import com.utopiaindustries.querybuilder.ctp.JobCardQueryBuilder; import com.utopiaindustries.querybuilder.ctp.JobCardQueryBuilder;
import com.utopiaindustries.querybuilder.ctp.PurchaseOrderCTPQueryBuilder; import com.utopiaindustries.querybuilder.ctp.PurchaseOrderCTPQueryBuilder;
import com.utopiaindustries.util.StringUtils; import com.utopiaindustries.util.StringUtils;
@ -32,7 +33,7 @@ public class PurchaseOrderCTPService {
} }
/* /*
* create new job card * create new purchase
* */ * */
public PurchaseOrderCTP createNewPurchaseOrderCTP() { public PurchaseOrderCTP createNewPurchaseOrderCTP() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
@ -43,27 +44,27 @@ public class PurchaseOrderCTPService {
} }
/* /*
* save card * save purchase order for ctp
* */ * */
@Transactional( rollbackFor = Exception.class ) @Transactional( rollbackFor = Exception.class )
public void save(PurchaseOrderCTP purchaseOrderCTP) { public void save(PurchaseOrderCTP purchaseOrderCTP) {
purchaseOrderCTPDao.save(purchaseOrderCTP); purchaseOrderCTPDao.save(purchaseOrderCTP);
} }
public List<PurchaseOrderCTP> getAllPurchaseOrderCtp(String purchaseOrderCode, String articleName, String StartDate, String EndDate, Long limit) { public List<PurchaseOrderCTP> getAllPurchaseOrderCtp(String purchaseOrderCode, String articleName, String startDate, String endDate, Long limit) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
List<PurchaseOrderCTP> list = new ArrayList<>(); List<PurchaseOrderCTP> list = new ArrayList<>();
String createdBy = authentication.getName(); String createdBy = authentication.getName();
if( limit == null ){ if( limit == null ){
limit = 100L; limit = 100L;
} }
if( !StringUtils.isAnyNotNullOrEmpty(purchaseOrderCode, articleName) ){ if(StringUtils.isAnyNotNullOrEmpty(purchaseOrderCode, articleName,startDate,endDate) ){
for (GrantedAuthority role : authentication.getAuthorities()){ for (GrantedAuthority role : authentication.getAuthorities()){
if (role.toString().equals("ROLE_ADMIN")){ if (role.toString().equals("ROLE_ADMIN")){
createdBy = ""; createdBy = "";
} }
} }
String query = PurchaseOrderCTPQueryBuilder.buildQuery(purchaseOrderCode, articleName, createdBy, StartDate, EndDate, limit ); String query = PurchaseOrderCTPQueryBuilder.buildQuery(purchaseOrderCode, articleName, createdBy, startDate, endDate, limit );
System.out.println( query ); System.out.println( query );
list = purchaseOrderCTPDao.findByQuery( query ); list = purchaseOrderCTPDao.findByQuery( query );
}else { }else {
@ -72,4 +73,8 @@ public class PurchaseOrderCTPService {
return list; return list;
} }
public List<PurchaseOrderCTP> findByTerm(String term ){
return purchaseOrderCTPDao.findByTerm( term );
}
} }

View File

@ -219,7 +219,11 @@
data: { data: {
jobCard: {}, jobCard: {},
items: [], items: [],
}, purchaseOrderID:0,
articleName: '',
purchaseOrderQuantity: 0,
purchaseOrderCode: '',
},
methods: { methods: {
addItem: function (e) { addItem: function (e) {
e.preventDefault(); e.preventDefault();
@ -259,10 +263,19 @@
} }
} }
return false; return false;
}, onPoSelect(id,purchaseOrder) {
this.purchaseOrderID = id,
this.articleName = purchaseOrder.articleName,
this.purchaseOrderQuantity = purchaseOrder.purchaseOrderQuantity,
this.purchaseOrderCode = purchaseOrder.purchaseOrderCode
} }
}, },
mounted: function () { mounted: function () {
this.jobCard = window.ctp.jobCard; this.jobCard = window.ctp.jobCard;
this.purchaseOrderID = this.jobCard.purchaseOrderId,
this.articleName = this.jobCard.articleName,
this.purchaseOrderQuantity = this.jobCard.poQuantity,
this.purchaseOrderCode = this.jobCard.purchaseOrderTitle
this.items = this.jobCard.items; this.items = this.jobCard.items;
} }

View File

@ -3592,8 +3592,50 @@ if ( typeof Vue !== 'undefined' ) {
}); });
/*
* search po
* */
Vue.component('search-ctp-po',{
mixins: [searchComponentMixin],
data: {
purchaseOrderCode: '',
},
methods : {
getSearchUrl : function () {
return `/ctp/rest/purchase-orders/ctp-po-search?term=${encodeURIComponent( this.list.term )}`
},
getEmittedEventName: function() {
return 'select-po';
},
getTitle: function( po ) {
this.purchaseOrderCode = po.purchaseOrderCode;
return this.purchaseOrderCode;
}
},
props: {
labelText: {
default: 'Search PO '
},
titleFieldName: {
default: 'poCode'
},
idFieldName: {
default: 'poId'
},
codeFieldName : {
default : 'poCode'
},
received : {
default : false
},
inputMode: {
default : 'none'
},
}
})
Vue.component('search-item', {
Vue.component('search-item', {
props: { props: {
label: { label: {
type: String, type: String,

View File

@ -31,10 +31,10 @@
<img th:src="@{/img/utopia-industries-white.svg}" class="page-header__logo" alt="Utopia Industries"> <img th:src="@{/img/utopia-industries-white.svg}" class="page-header__logo" alt="Utopia Industries">
</a> </a>
<ul class="navbar-nav"> <ul class="navbar-nav">
<!-- <li class="nav-item" sec:authorize="hasAnyRole('ROLE_PURCHASE_ORDER', 'ROLE_ADMIN')">--> <li class="nav-item" sec:authorize="hasAnyRole('ROLE_PURCHASE_ORDER', 'ROLE_ADMIN')">
<!-- <a th:href="@{/purchase-order/}" class="nav-link"--> <a th:href="@{/purchase-order/}" class="nav-link"
<!-- th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/purchase-order') ? 'active' : ''}">Purchase Order</a>--> th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/purchase-order') ? 'active' : ''}">Purchase Order</a>
<!-- </li>--> </li>
<li class="nav-item" sec:authorize="hasAnyRole('ROLE_JOB_CARD', 'ROLE_ADMIN')"> <li class="nav-item" sec:authorize="hasAnyRole('ROLE_JOB_CARD', 'ROLE_ADMIN')">
<a th:href="@{/job-cards/}" class="nav-link" <a th:href="@{/job-cards/}" class="nav-link"
th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/job-cards') ? 'active' : ''}">Job Cards</a> th:classappend="${#strings.startsWith(#httpServletRequest.getRequestURI(), '/ctp/job-cards') ? 'active' : ''}">Job Cards</a>

View File

@ -1,98 +1,113 @@
<!DOCTYPE html> <!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"> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml"
<head th:replace="_fragments :: head('Home Page')"></head> xmlns:v-bind="http://www.w3.org/1999/xhtml" xmlns:ctp="http://www.w3.org/1999/xhtml">
<body> <head th:replace="_fragments :: head('Home Page')"></head>
<div class="container-fluid"> <body>
<header class="row page-header" th:replace="_fragments :: page-header"></header> <div class="container-fluid">
<main class="row page-main"> <header class="row page-header" th:replace="_fragments :: page-header"></header>
<div class="col-sm" th:fragment="cardFragment"> <main class="row page-main">
<div th:replace="_notices :: page-notices"></div> <div class="col-sm" th:fragment="cardFragment">
<form th:action="@{ ${jobCard.id} ? ('/job-cards/edit/' + ${jobCard.id}) : '/job-cards/edit' }" <div th:replace="_notices :: page-notices"></div>
method="POST" <form th:action="@{ ${jobCard.id} ? ('/job-cards/edit/' + ${jobCard.id}) : '/job-cards/edit' }"
th:object="${jobCard}" method="POST"
id="jobCardApp"> th:object="${jobCard}"
<input hidden="hidden" th:field="*{id}"> id="jobCardApp">
<input hidden="hidden" th:field="*{code}"> <input hidden="hidden" th:field="*{id}">
<input hidden="hidden" th:field="*{createdAt}"> <input hidden="hidden" th:field="*{code}">
<input hidden="hidden" th:field="*{createdBy}"> <input hidden="hidden" th:field="*{createdAt}">
<input hidden="hidden" th:field="*{status}"> <input hidden="hidden" th:field="*{createdBy}">
<input hidden="hidden" th:field="*{inventoryStatus}"> <input hidden="hidden" th:field="*{status}">
<div class="bg-light p-3 mb-3"> <input hidden="hidden" th:field="*{inventoryStatus}">
<h6 class="mb-3">Info</h6>
<div class="form-row"> <!-- Hidden Inputs for Dynamic Values -->
<div class="col-sm-3 form-group"> <input type="hidden" name="articleName" :value="articleName">
<label>Job Order</label> <input type="hidden" name="poQuantity" :value="purchaseOrderQuantity">
<input type="number" class="form-control" th:field="*{jobOrderId}" required> <input type="hidden" name="purchaseOrderTitle" :value="purchaseOrderCode">
</div> <input type="hidden" name="purchaseOrderId" :value="purchaseOrderID">
<div class="col-sm-3 form-group">
<label>Customer</label> <div class="bg-light p-3 mb-3">
<input class="form-control" th:field="*{customer}" required> <h6 class="mb-3">Info</h6>
</div> <div class="form-row">
<div class="col-sm-3 form-group"> <div class="col-sm-3 form-group">
<label>Lot Number</label> <label>Job Order</label>
<input class="form-control" th:field="*{lotNumber}" required> <input type="number" class="form-control" th:field="*{jobOrderId}" required>
</div>
<div class="col-sm-3 form-group">
<label>Article Name</label>
<input class="form-control" th:field="*{articleName}" required>
</div>
</div>
<div class="form-row">
<div class="col-sm-3 form-group">
<label>Purchase Order</label>
<input type="text" class="form-control" th:field="*{purchaseOrderId}" required>
</div>
<div class="col-sm-3 form-group">
<label>PO Quantity</label>
<input type="number" class="form-control" th:field="*{poQuantity}" required>
</div>
<div class="col-sm-3 form-group" th:with="title=*{locationTitle},id=*{locationSiteId}">
<location-site-search th:attr="id=${id},title=${title}"
v-bind:id-field-name="'locationSiteId'">
</location-site-search>
</div>
<div class="col-sm-3 form-group">
<label>Generated Date</label>
<span class="form-control" ctp:formatdatetime="*{createdAt}" readonly>
</div>
</div>
<div class="form-row">
<div class="col-sm-6 form-group">
<label>Description*</label>
<textarea class="form-control" rows="2" th:field="*{description}"></textarea>
</div>
</div>
</div> </div>
<div class="bg-light p-3 mb-3"> <div class="col-sm-3 form-group">
<h6 class="mb-3">Items</h6> <label>Customer</label>
<job-card-item <input class="form-control" th:field="*{customer}" required>
v-for="(item,index) in items"
v-bind:item="item"
v-bind:key="index"
v-bind:index="index"
v-bind:items.sync="items">
</job-card-item>
<div class="alert alert-danger" v-if="hasDuplicates()"><b>Duplicate Items Selected</b></div>
<button class="btn btn-secondary btn-sm" v-on:click="addItem">
Add Item
</button>
</div> </div>
<div> <div class="col-sm-3 form-group">
<button class="btn btn-secondary" type="submit" name="user" value="draft" v-bind:disabled="hasEmptyItems()">Save Draft</button> <label>Lot Number</label>
<button class="btn btn-primary" type="submit" name="user" value="post" v-bind:disabled="hasEmptyItems()">Post</button> <input class="form-control" th:field="*{lotNumber}" required>
<a th:href="@{/job-cards}" class="btn btn-light">Cancel</a>
</div> </div>
</form> <div class="col-sm-3 form-group">
<script th:inline="javascript"> <label>Article Name</label>
window.ctp.jobCard = [[${jobCard}]]; <!-- Dynamically show articleName -->
window.ctp.types = [[${cutPieceTypes}]] <span class="form-control">{{ articleName || jobCard.articleName }}</span>
window.ctp.accounts = [[${accounts}]] </div>
</script> </div>
<script th:src="@{/js/vendor/compressor.min.js}"></script>
<script th:src="@{/js/job-card-form.js}"></script> <div class="form-row">
<search-ctp-po
v-bind:id-field-name="'poID'"
v-on:select-po="onPoSelect"
v-bind:required="false"
v-bind:selected="purchaseOrderCode">
</search-ctp-po>
<div class="col-sm-3 form-group">
<label>PO Quantity</label>
<!-- Dynamically show PO quantity -->
<span class="form-control">{{ purchaseOrderQuantity || jobCard.poQuantity }}</span>
</div>
<div class="col-sm-3 form-group" th:with="title=*{locationTitle},id=*{locationSiteId}">
<location-site-search th:attr="id=${id},title=${title}" v-bind:id-field-name="'locationSiteId'">
</location-site-search>
</div>
<div class="col-sm-3 form-group">
<label>Generated Date</label>
<!-- Use Thymeleaf to format datetime -->
<span class="form-control" ctp:formatdatetime="*{createdAt}" readonly></span>
</div>
</div>
<div class="form-row">
<div class="col-sm-6 form-group">
<label>Description*</label>
<textarea class="form-control" rows="2" th:field="*{description}"></textarea>
</div>
</div>
</div> </div>
</main> <div class="bg-light p-3 mb-3">
<h6 class="mb-3">Items</h6>
<job-card-item
v-for="(item, index) in items"
v-bind:item="item"
v-bind:key="index"
v-bind:index="index"
v-bind:items.sync="items">
</job-card-item>
<div class="alert alert-danger" v-if="hasDuplicates()"><b>Duplicate Items Selected</b></div>
<button class="btn btn-secondary btn-sm" v-on:click="addItem">
Add Item
</button>
</div>
<div>
<button class="btn btn-secondary" type="submit" name="user" value="draft" v-bind:disabled="hasEmptyItems()">Save Draft</button>
<button class="btn btn-primary" type="submit" name="user" value="post" v-bind:disabled="hasEmptyItems()">Post</button>
<a th:href="@{/job-cards}" class="btn btn-light">Cancel</a>
</div>
</form>
<script th:inline="javascript">
window.ctp.jobCard = [[${jobCard}]];
window.ctp.types = [[${cutPieceTypes}]];
window.ctp.accounts = [[${accounts}]];
</script>
<script th:src="@{/js/vendor/compressor.min.js}"></script>
<script th:src="@{/js/job-card-form.js}"></script>
</div> </div>
<div th:replace="_fragments :: page-footer-scripts"></div> </main>
</body> </div>
</html> <div th:replace="_fragments :: page-footer-scripts"></div>
</body>
</html>

View File

@ -1,37 +1,57 @@
<!DOCTYPE html> <!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"> <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" xmlns:ctp="http://www.w3.org/1999/xhtml">
<head th:replace="_fragments :: head('Home Page')"></head> <head th:replace="_fragments :: head('Home Page')"></head>
<body> <body>
<div class="container-fluid"> <div class="container-fluid">
<header class="row page-header" th:replace="_fragments :: page-header"></header> <header class="row page-header" th:replace="_fragments :: page-header"></header>
<main class="row page-main"> <main class="row page-main">
<div class="col-sm" th:fragment="cardFragment"> <div class="col-sm" th:fragment="cardFragment">
<div th:replace="_notices :: page-notices"></div> <div th:replace="_notices :: page-notices"></div>
<form th:action="@{ ${purchaseOrderCTP.id} ? ('/purchase-order/edit/' + ${purchaseOrderCTP.id}) : '/purchase-order/edit' }" <form th:action="${purchaseOrder.id} != null ? @{/purchase-order/edit/(id=${purchaseOrder.id})} : @{/purchase-order/edit}"
method="POST" method="POST"
th:object="${purchaseOrderCTP}" th:object="${purchaseOrder}"
id="purchaseOrderApp"> id="jobCardApp">
<input hidden="hidden" th:field="*{id}"> <div class="bg-light p-3 mb-3">
<input hidden="hidden" th:field="*{order}"> <h6 class="mb-3">Info</h6>
<div class="bg-light p-3 mb-3"> <input hidden="hidden" th:field="${purchaseOrder.id}">
<h6 class="mb-3">Info</h6> <input hidden="hidden" th:field="${purchaseOrder.createdAt}">
<div class="form-row"> <input hidden="hidden" th:field="${purchaseOrder.createdBy}">
<div class="col-sm-3 form-group"> <div class="form-row">
<label>Job Order</label> <div class="col-sm-3 form-group">
<input type="number" class="form-control" th:field="*{jobOrderId}" required> <label>Purchase Order Code</label>
</div> <input class="form-control" th:field="${purchaseOrder.purchaseOrderCode}" required>
</div> </div>
</div> <div class="col-sm-3 form-group">
<label>Purchase Order Quantity</label>
<input class="form-control" th:field="${purchaseOrder.purchaseOrderQuantity}" required>
</div>
<div class="col-sm-3 form-group">
<label>Required Quantity</label>
<input class="form-control" th:field="${purchaseOrder.purchaseOrderQuantityRequired}" required>
</div>
<div class="col-sm-3 form-group">
<label>Article Name</label>
<input type="text" class="form-control" th:field="${purchaseOrder.articleName}" required>
</div>
</div>
</div>
<div> <div>
<button class="btn btn-secondary" type="submit" name="user" value="draft" v-bind:disabled="hasEmptyItems()">Save Draft</button> <button class="btn btn-secondary" type="submit" name="user" value="draft"
<button class="btn btn-primary" type="submit" name="user" value="post" v-bind:disabled="hasEmptyItems()">Post</button> v-bind:disabled="hasEmptyItems()">
<a th:href="@{/job-cards}" class="btn btn-light">Cancel</a> Save Draft
</div> </button>
</form> <button class="btn btn-primary" type="submit" name="user" value="post"
v-bind:disabled="hasEmptyItems()">
Post
</button>
<a th:href="@{/purchase-order}" class="btn btn-light">Cancel</a>
</div>
</form>
</div> </div>
</main> </main>
</div> </div>
<div th:replace="_fragments :: page-footer-scripts"></div> <div th:replace="_fragments :: page-footer-scripts"></div>
</body> </body>

View File

@ -0,0 +1,64 @@
<!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('Purchase Order')"></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="/purchaseOrder/purchase-order-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>Job Cards</h3>
<a th:href="@{/purchase-order/new}" class="btn btn-primary">Add New</a>
</div>
<div th:replace="_fragments :: table-loading-skeleton"></div>
<!-- Show table if purchaseOrder is not null and not empty -->
<table th:if="${purchaseOrder != null and !purchaseOrder.isEmpty()}" class="table table-striped font-sm" >
<thead>
<tr>
<th>PO Code</th>
<th>PO Quantity</th>
<th>Required Quantity</th>
<th>Article Name</th>
<th>Created At</th>
<th>Created By</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr th:each="order : ${purchaseOrder}" th:object="${purchaseOrder}">
<td th:text="${order.purchaseOrderCode}"></td>
<td th:text="${order.purchaseOrderQuantity}"></td>
<td th:text="${order.purchaseOrderQuantityRequired}"></td>
<td th:text="${order.articleName}"></td>
<td ctp:formatdatetime="${order.createdAt}"></td>
<td th:text="${order.createdBy}"></td>
<td>
<span class="badge font-sm" th:classappend="'badge-' + ${order.status}" th:if="${order.status}" th:text="${order.status}"></span>
<span th:unless="${order.status}">-</span>
</td>
<td>
<th:block >
<a th:href="@{'/purchase-order/edit/' + ${order.id}}" class="btn btn-sm btn-secondary" title="Edit">
<i class="bi bi-pencil"></i>
</a>
</th:block>
</td>
</tr>
</tbody>
</table>
<!-- Show message if purchaseOrder is null or empty -->
<h4 th:if="${purchaseOrder == null or purchaseOrder.isEmpty()}">No cards found.</h4>
</div>
</main>
</div>
<div th:replace="_fragments :: page-footer-scripts"></div>
</body>
</html>

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">
<head th:replace="_fragments :: head('Home Page')"></head>
<body>
<!-- sidebar starts -->
<aside class="col-sm-2" th:fragment="sidebar">
<div class="page-filters-sidebar">
<form th:action="@{${#strings.replace(#httpServletRequest.requestURI, #request.getContextPath(), '')}}">
<h5 class="mb-4">Refine Your Search</h5>
<div class="form-group">
<label>PO Code</label>
<input type="text" class="form-control" name="purchaseOrderCode" maxlength="100" th:value="${param['purchaseOrderCode']}">
</div>
<div class="form-group">
<label>Article Name</label>
<input type="text" class="form-control" name="articleName" maxlength="100" th:value="${param['articleName']}">
</div>
<div class="form-group">
<label>From Date</label>
<input type="date" class="form-control" name="created-start-date" th:value="${param['created-start-date'] ?: startDate }" >
</div>
<div class="form-group">
<label>To Date</label>
<input type="date" class="form-control" name="created-end-date" th:value="${param['created-end-date'] ?: endDate}" >
</div>
<div class="form-group">
<label>Count</label>
<input type="number" class="form-control" name="limit"
th:value="${param['limits'] ?: 100}">
</div>
<input type="submit" class="btn btn-secondary btn-block" value="Search">
<a th:href="@{${#strings.replace(#httpServletRequest.requestURI, #request.getContextPath(), '')}}"
class="btn btn-secondary btn-block">Reset</a>
</form>
</div>
</aside>
<!-- sidebar ends -->
<div th:fragment="page-footer-scripts">
<script th:src="@{/js/vendor/lazyload-db.js}"></script>
<script th:src="@{/js/main.js}"></script>
</div>
</body>
</html>

View File

@ -10,56 +10,71 @@
<main class="row page-main"> <main class="row page-main">
<aside class="col-sm-2" th:replace="/reporting/inventory-summary-sidebar :: sidebar"></aside> <aside class="col-sm-2" th:replace="/reporting/inventory-summary-sidebar :: sidebar"></aside>
<div class="col-sm"> <div class="col-lg-10">
<h3>Summary</h3> <h3>Summary</h3>
<table th:if="${tableData != null}" class=" table table-striped table-bordered table-hover font-sm " data-table data-order="[[ 0, &quot;asc&quot; ]]"> <!-- Scrollable wrapper to prevent overflow -->
<thead> <div class="table-responsive">
<tr> <table th:if="${tableData != null}"
<th colspan="1" >SKU / Date</th> class="table table-striped table-bordered table-hover font-sm"
<th class="text-center text-center" th:each="date: ${dateLimits}" > data-table
<span class="d-block " style="width: 100px !important; padding-left: 70px; " th:text="${#temporals.format(date, 'E')}"></span> data-order="[[ 0, &quot;asc&quot; ]]"
<span class="d-block pl-4 " style="width: 150px !important; " ctp:formatdate="${date}" ></span> style="min-width: 1000px;">
</th> <thead>
</tr> <tr>
</thead> <th colspan="1">SKU / Date</th>
<tbody> <th class="text-center" th:each="date: ${dateLimits}">
<tr th:each="sku : ${tableData.keySet()}"> <span class="d-block" style="width: 100px !important; padding-left: 70px;"
<!-- SKU Column (first td) --> th:text="${#temporals.format(date, 'E')}"></span>
<td th:rowspan="${#lists.size(sku)}" th:text="${sku}" class="align-middle" rowspan="3" ></td> <span class="d-block pl-4" style="width: 150px !important;"
<td th:each="dates: ${dateLimits}" class="p-0 "> ctp:formatdate="${date}"></span>
<table th:each="data : ${tableData.get(sku)}" class="table table-striped table-bordered table-hover font-sm m-0 " > </th>
<thead th:if="${data.getKey() == dates.toString()}"> </tr>
<tr> </thead>
<th>ID</th> <tbody>
<th>IN</th> <tr th:each="sku : ${tableData.keySet()}">
<th>OUT</th> <!-- SKU Column -->
<th>BALANCE</th> <td th:rowspan="${#lists.size(sku)}"
</tr> th:text="${sku}"
</thead> class="align-middle"
<tbody > rowspan="3"></td>
<tr th:each="reportSummary : ${data.getValue()}" rowspan="3" > <td th:each="dates: ${dateLimits}" class="p-0">
<td th:if="${data.getKey() == dates.toString()}" > <table th:each="data : ${tableData.get(sku)}"
class="table table-striped table-bordered table-hover font-sm m-0">
<thead th:if="${data.getKey() == dates.toString()}">
<tr>
<th>ID</th>
<th>IN</th>
<th>OUT</th>
<th>BALANCE</th>
</tr>
</thead>
<tbody>
<tr th:each="reportSummary : ${data.getValue()}" rowspan="3">
<td th:if="${data.getKey() == dates.toString()}">
<span th:text="${reportSummary.getParentDocumentType()} + ' ' + ${reportSummary.getParentDocumentPieceType()}"></span> <span th:text="${reportSummary.getParentDocumentType()} + ' ' + ${reportSummary.getParentDocumentPieceType()}"></span>
</td> </td>
<td th:if="${data.getKey() == dates.toString()}" class="w-25 text-center" > <td th:if="${data.getKey() == dates.toString()}" class="w-25 text-center">
<span th:text="${reportSummary.getTotalIn()}"></span> <span th:text="${reportSummary.getTotalIn()}"></span>
</td> </td>
<td th:if="${data.getKey() == dates.toString()}" class="w-25 text-center" > <td th:if="${data.getKey() == dates.toString()}" class="w-25 text-center">
<span th:text="${reportSummary.getTotalOut()}"></span> <span th:text="${reportSummary.getTotalOut()}"></span>
</td> </td>
<td th:if="${data.getKey() == dates.toString()}" class="w-25 text-center" > <td th:if="${data.getKey() == dates.toString()}" class="w-25 text-center">
<span th:text="${reportSummary.getTotalIn() - reportSummary.getTotalOut()}"></span> <span th:text="${reportSummary.getTotalIn() - reportSummary.getTotalOut()}"></span>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
<h4 th:if="${tableData == null}"> No data found </h4> <h4 th:if="${tableData == null}"> No data found </h4>
</div> </div>
</main> </main>
</div> </div>

View File

@ -9,11 +9,9 @@
<header class="row page-header" th:replace="_fragments :: page-header"></header> <header class="row page-header" th:replace="_fragments :: page-header"></header>
<main class="row page-main"> <main class="row page-main">
<aside class="col-sm-2" th:replace="/reporting/po-report-sidebar :: sidebar"></aside> <aside class="col-sm-2" th:replace="/reporting/po-report-sidebar :: sidebar"></aside>
<div class="col-sm"> <div class="col-lg-10 col-sm-10">
<h3>PO's Report</h3> <h3>PO's Report</h3>
<div th:replace="_notices :: page-notices"></div> <table class="table table-striped font-sm" data-order="[[ 0, &quot;asc&quot; ]]">
<div th:replace="_fragments :: table-loading-skeleton"></div>
<table class="table table-striped font-sm" data-table data-order="[[ 0, &quot;asc&quot; ]]">
<thead> <thead>
<tr> <tr>
<th>PO Number</th> <th>PO Number</th>
@ -54,8 +52,8 @@
</tbody> </tbody>
</table> </table>
</div>
<!-- <h4 th:if="${#lists.size(cards) == 0 }">No cards found.</h4>--> <!-- <h4 th:if="${#lists.size(cards) == 0 }">No cards found.</h4>-->
</div>
</main> </main>
</div> </div>