mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'fix/BE/UUIT-Recording-closing-report-uniformity-dashboard' into 'development'
Fix/be/uuit recording and dashboard See merge request mbugroup/lti-api!250
This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
commonService "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
rDashboard "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/repositories"
|
rDashboard "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/repositories"
|
||||||
sDashboard "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/services"
|
sDashboard "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/services"
|
||||||
|
|
||||||
@@ -16,11 +18,12 @@ type DashboardModule struct{}
|
|||||||
|
|
||||||
func (DashboardModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
func (DashboardModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
dashboardRepo := rDashboard.NewDashboardRepository(db)
|
dashboardRepo := rDashboard.NewDashboardRepository(db)
|
||||||
|
hppCostRepo := commonRepo.NewHppCostRepository(db)
|
||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
|
||||||
dashboardService := sDashboard.NewDashboardService(dashboardRepo, validate)
|
hppSvc := commonService.NewHppService(hppCostRepo)
|
||||||
|
dashboardService := sDashboard.NewDashboardService(dashboardRepo, validate, hppSvc)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
DashboardRoutes(router, userService, dashboardService)
|
DashboardRoutes(router, userService, dashboardService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type DashboardRepository interface {
|
|||||||
SumSellingPrice(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (SellingPriceAggregate, error)
|
SumSellingPrice(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (SellingPriceAggregate, error)
|
||||||
SumEggProductionWeightGrams(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error)
|
SumEggProductionWeightGrams(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error)
|
||||||
SumEggProductionWeightKg(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error)
|
SumEggProductionWeightKg(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) (float64, error)
|
||||||
|
ListProjectFlockKandangIDsByEggProduction(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]uint, error)
|
||||||
GetRecordingWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]RecordingWeeklyMetric, error)
|
GetRecordingWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]RecordingWeeklyMetric, error)
|
||||||
GetUniformityWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]UniformityWeeklyMetric, error)
|
GetUniformityWeeklyMetrics(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]UniformityWeeklyMetric, error)
|
||||||
GetStandardWeeklyMetrics(ctx context.Context, weeks []int, filters *validation.DashboardFilter) ([]StandardWeeklyMetric, error)
|
GetStandardWeeklyMetrics(ctx context.Context, weeks []int, filters *validation.DashboardFilter) ([]StandardWeeklyMetric, error)
|
||||||
|
|||||||
@@ -309,6 +309,27 @@ func (r *DashboardRepositoryImpl) SumEggProductionWeightKg(ctx context.Context,
|
|||||||
return grams / 1000, nil
|
return grams / 1000, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *DashboardRepositoryImpl) ListProjectFlockKandangIDsByEggProduction(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]uint, error) {
|
||||||
|
var ids []uint
|
||||||
|
|
||||||
|
db := r.DB().WithContext(ctx).
|
||||||
|
Table("recording_eggs AS re").
|
||||||
|
Select("DISTINCT r.project_flock_kandangs_id").
|
||||||
|
Joins("JOIN recordings AS r ON r.id = re.recording_id").
|
||||||
|
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||||
|
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||||
|
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
|
||||||
|
Where("r.deleted_at IS NULL")
|
||||||
|
|
||||||
|
db = applyDashboardFilters(db, filters)
|
||||||
|
|
||||||
|
if err := db.Scan(&ids).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *DashboardRepositoryImpl) GetFeedUsageByUom(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]FeedUsageByUom, error) {
|
func (r *DashboardRepositoryImpl) GetFeedUsageByUom(ctx context.Context, start, end time.Time, filters *validation.DashboardFilter) ([]FeedUsageByUom, error) {
|
||||||
var rows []FeedUsageByUom
|
var rows []FeedUsageByUom
|
||||||
|
|
||||||
@@ -553,7 +574,7 @@ func (r *DashboardRepositoryImpl) GetComparisonWeeklyMetrics(ctx context.Context
|
|||||||
var rows []ComparisonWeeklyMetric
|
var rows []ComparisonWeeklyMetric
|
||||||
db := r.DB().WithContext(ctx).
|
db := r.DB().WithContext(ctx).
|
||||||
Table("recordings AS r").
|
Table("recordings AS r").
|
||||||
Select(fmt.Sprintf(`((r.day - 1) / 7 + 1) AS week,
|
Select(fmt.Sprintf(`(CASE WHEN r.day IS NULL OR r.day <= 0 THEN 1 ELSE ((r.day - 1) / 7 + 1) END) AS week,
|
||||||
%s AS series_id,
|
%s AS series_id,
|
||||||
COALESCE(AVG(%s), 0) AS value`, seriesExpr, metricExpr)).
|
COALESCE(AVG(%s), 0) AS value`, seriesExpr, metricExpr)).
|
||||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||||
@@ -561,8 +582,7 @@ func (r *DashboardRepositoryImpl) GetComparisonWeeklyMetrics(ctx context.Context
|
|||||||
Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
|
Joins("JOIN project_flocks AS pf ON pf.id = pfk.project_flock_id").
|
||||||
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
Joins("JOIN locations AS loc ON loc.id = k.location_id").
|
||||||
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
|
Where("r.record_datetime >= ? AND r.record_datetime < ?", start, end).
|
||||||
Where("r.deleted_at IS NULL").
|
Where("r.deleted_at IS NULL")
|
||||||
Where("r.day IS NOT NULL AND r.day > 0")
|
|
||||||
|
|
||||||
db = applyDashboardFilters(db, filters)
|
db = applyDashboardFilters(db, filters)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
commonService "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/dto"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/validations"
|
||||||
@@ -27,13 +28,15 @@ type dashboardService struct {
|
|||||||
Log *logrus.Logger
|
Log *logrus.Logger
|
||||||
Validate *validator.Validate
|
Validate *validator.Validate
|
||||||
Repository repository.DashboardRepository
|
Repository repository.DashboardRepository
|
||||||
|
HppSvc commonService.HppService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDashboardService(repo repository.DashboardRepository, validate *validator.Validate) DashboardService {
|
func NewDashboardService(repo repository.DashboardRepository, validate *validator.Validate, hppSvc commonService.HppService) DashboardService {
|
||||||
return &dashboardService{
|
return &dashboardService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
Repository: repo,
|
Repository: repo,
|
||||||
|
HppSvc: hppSvc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,13 +595,13 @@ func buildAggregateComparisonPercent(weeks []int, seriesRows []repository.Compar
|
|||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
|
|
||||||
if count == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if result[week] == nil {
|
if result[week] == nil {
|
||||||
result[week] = map[uint]float64{}
|
result[week] = map[uint]float64{}
|
||||||
}
|
}
|
||||||
|
if count == 0 {
|
||||||
|
result[week][series.Id] = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
result[week][series.Id] = sum / count
|
result[week][series.Id] = sum / count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -846,6 +849,21 @@ func percentDelta(current, last float64) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s dashboardService) calculateHppGlobal(ctx context.Context, startDate, endExclusive, endDate time.Time, location *time.Location) (float64, float64, error) {
|
func (s dashboardService) calculateHppGlobal(ctx context.Context, startDate, endExclusive, endDate time.Time, location *time.Location) (float64, float64, error) {
|
||||||
|
if s.HppSvc != nil {
|
||||||
|
currentHpp, err := s.hppGlobalForPeriod(ctx, startDate, endExclusive)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMonthStart, lastMonthEndExclusive := monthRange(endDate.AddDate(0, -1, 0), location)
|
||||||
|
lastHpp, err := s.hppGlobalForPeriod(ctx, lastMonthStart, lastMonthEndExclusive)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentHpp, lastHpp, nil
|
||||||
|
}
|
||||||
|
|
||||||
totalEggKg, err := s.Repository.SumEggProductionWeightKg(ctx, startDate, endExclusive, nil)
|
totalEggKg, err := s.Repository.SumEggProductionWeightKg(ctx, startDate, endExclusive, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
@@ -878,6 +896,37 @@ func (s dashboardService) calculateHppGlobal(ctx context.Context, startDate, end
|
|||||||
return hppCurrent, hppLast, nil
|
return hppCurrent, hppLast, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s dashboardService) hppGlobalForPeriod(ctx context.Context, startDate, endExclusive time.Time) (float64, error) {
|
||||||
|
kandangIDs, err := s.Repository.ListProjectFlockKandangIDsByEggProduction(ctx, startDate, endExclusive, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(kandangIDs) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
endOfPeriod := endExclusive.Add(-time.Nanosecond)
|
||||||
|
totalCost := 0.0
|
||||||
|
totalWeightKg := 0.0
|
||||||
|
for _, kandangID := range kandangIDs {
|
||||||
|
hppCost, err := s.HppSvc.CalculateHppCost(kandangID, &endOfPeriod)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if hppCost == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalCost += hppCost.Estimation.Total
|
||||||
|
totalWeightKg += hppCost.Estimation.Kg
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalWeightKg <= 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalCost / totalWeightKg, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s dashboardService) calculateSellingPrice(ctx context.Context, endDate time.Time, location *time.Location) (float64, float64, error) {
|
func (s dashboardService) calculateSellingPrice(ctx context.Context, endDate time.Time, location *time.Location) (float64, float64, error) {
|
||||||
startPrevMonth, endPrevMonthExclusive := monthRange(endDate.AddDate(0, -1, 0), location)
|
startPrevMonth, endPrevMonthExclusive := monthRange(endDate.AddDate(0, -1, 0), location)
|
||||||
currentEndExclusive := endDate.AddDate(0, 0, 1)
|
currentEndExclusive := endDate.AddDate(0, 0, 1)
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
}
|
}
|
||||||
|
|
||||||
mappedDepletions := recordingutil.MapDepletions(createdRecording.Id, req.Depletions)
|
mappedDepletions := recordingutil.MapDepletions(createdRecording.Id, req.Depletions)
|
||||||
|
depletionDesired := resetDepletionQuantitiesForFIFO(mappedDepletions, s.FifoSvc != nil)
|
||||||
if s.FifoSvc != nil && len(mappedDepletions) > 0 {
|
if s.FifoSvc != nil && len(mappedDepletions) > 0 {
|
||||||
sourceWarehouseID, err := s.resolvePopulationWarehouseID(ctx, req.ProjectFlockKandangId)
|
sourceWarehouseID, err := s.resolvePopulationWarehouseID(ctx, req.ProjectFlockKandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -301,6 +302,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if s.FifoSvc != nil {
|
if s.FifoSvc != nil {
|
||||||
|
applyDepletionDesiredQuantities(mappedDepletions, depletionDesired, true)
|
||||||
note := fmt.Sprintf("Recording-Create#%d", createdRecording.Id)
|
note := fmt.Sprintf("Recording-Create#%d", createdRecording.Id)
|
||||||
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil {
|
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -465,6 +467,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
mappedDepletions := recordingutil.MapDepletions(recordingEntity.Id, req.Depletions)
|
mappedDepletions := recordingutil.MapDepletions(recordingEntity.Id, req.Depletions)
|
||||||
|
depletionDesired := resetDepletionQuantitiesForFIFO(mappedDepletions, s.FifoSvc != nil)
|
||||||
if s.FifoSvc != nil && len(mappedDepletions) > 0 {
|
if s.FifoSvc != nil && len(mappedDepletions) > 0 {
|
||||||
sourceWarehouseID, err := s.resolvePopulationWarehouseID(ctx, recordingEntity.ProjectFlockKandangId)
|
sourceWarehouseID, err := s.resolvePopulationWarehouseID(ctx, recordingEntity.ProjectFlockKandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -480,6 +483,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.FifoSvc != nil {
|
if s.FifoSvc != nil {
|
||||||
|
applyDepletionDesiredQuantities(mappedDepletions, depletionDesired, true)
|
||||||
note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
|
note := fmt.Sprintf("Recording-Edit#%d", recordingEntity.Id)
|
||||||
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil {
|
if err := s.consumeRecordingDepletions(ctx, tx, mappedDepletions, note, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -929,6 +933,9 @@ func (s *recordingService) consumeRecordingDepletions(
|
|||||||
|
|
||||||
destDelta := depletion.Qty + depletion.PendingQty
|
destDelta := depletion.Qty + depletion.PendingQty
|
||||||
if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
|
if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
|
||||||
|
if depletion.ProductWarehouseId == sourceWarehouseID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
log := &entity.StockLog{
|
log := &entity.StockLog{
|
||||||
ProductWarehouseId: depletion.ProductWarehouseId,
|
ProductWarehouseId: depletion.ProductWarehouseId,
|
||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
@@ -1066,6 +1073,9 @@ func (s *recordingService) releaseRecordingDepletions(
|
|||||||
|
|
||||||
destDelta := depletion.Qty + depletion.PendingQty
|
destDelta := depletion.Qty + depletion.PendingQty
|
||||||
if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
|
if depletion.ProductWarehouseId != 0 && destDelta > 0 && strings.TrimSpace(note) != "" && actorID != 0 {
|
||||||
|
if depletion.ProductWarehouseId == sourceWarehouseID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
log := &entity.StockLog{
|
log := &entity.StockLog{
|
||||||
ProductWarehouseId: depletion.ProductWarehouseId,
|
ProductWarehouseId: depletion.ProductWarehouseId,
|
||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
@@ -1235,6 +1245,11 @@ type desiredStock struct {
|
|||||||
Pending float64
|
Pending float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type desiredDepletion struct {
|
||||||
|
Qty float64
|
||||||
|
Pending float64
|
||||||
|
}
|
||||||
|
|
||||||
func resetStockQuantitiesForFIFO(stocks []entity.RecordingStock, enabled bool) []desiredStock {
|
func resetStockQuantitiesForFIFO(stocks []entity.RecordingStock, enabled bool) []desiredStock {
|
||||||
desired := make([]desiredStock, len(stocks))
|
desired := make([]desiredStock, len(stocks))
|
||||||
for i := range stocks {
|
for i := range stocks {
|
||||||
@@ -1269,6 +1284,33 @@ func applyStockDesiredQuantities(stocks []entity.RecordingStock, desired []desir
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetDepletionQuantitiesForFIFO(depletions []entity.RecordingDepletion, enabled bool) []desiredDepletion {
|
||||||
|
desired := make([]desiredDepletion, len(depletions))
|
||||||
|
for i := range depletions {
|
||||||
|
desired[i].Qty = depletions[i].Qty
|
||||||
|
desired[i].Pending = depletions[i].PendingQty
|
||||||
|
if !enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
depletions[i].Qty = 0
|
||||||
|
depletions[i].PendingQty = 0
|
||||||
|
}
|
||||||
|
return desired
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyDepletionDesiredQuantities(depletions []entity.RecordingDepletion, desired []desiredDepletion, enabled bool) {
|
||||||
|
if !enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range depletions {
|
||||||
|
if i >= len(desired) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
depletions[i].Qty = desired[i].Qty
|
||||||
|
depletions[i].PendingQty = desired[i].Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *recordingService) syncRecordingStocks(
|
func (s *recordingService) syncRecordingStocks(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
tx *gorm.DB,
|
tx *gorm.DB,
|
||||||
|
|||||||
Reference in New Issue
Block a user