mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Feat[BE]: refactor stock log handling and introduce new log types for adjustments and transfers
This commit is contained in:
@@ -505,12 +505,25 @@ func (s *fifoService) fetchStockLots(ctx context.Context, tx *gorm.DB, productWa
|
|||||||
|
|
||||||
var lots []stockLot
|
var lots []stockLot
|
||||||
for key, cfg := range configs {
|
for key, cfg := range configs {
|
||||||
selectStmt := fmt.Sprintf(
|
|
||||||
"%s AS id, %s AS available_qty, %s AS created_at",
|
usesNumericTime := cfg.Columns.CreatedAt == cfg.Columns.ID
|
||||||
cfg.Columns.ID,
|
|
||||||
fmt.Sprintf("%s - COALESCE(%s,0)", cfg.Columns.TotalQuantity, cfg.Columns.TotalUsedQuantity),
|
var selectStmt string
|
||||||
cfg.Columns.CreatedAt,
|
if usesNumericTime {
|
||||||
)
|
|
||||||
|
selectStmt = fmt.Sprintf(
|
||||||
|
"%s AS id, %s AS available_qty, '1970-01-01 00:00:00 UTC'::timestamp AS created_at",
|
||||||
|
cfg.Columns.ID,
|
||||||
|
fmt.Sprintf("%s - COALESCE(%s,0)", cfg.Columns.TotalQuantity, cfg.Columns.TotalUsedQuantity),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
selectStmt = fmt.Sprintf(
|
||||||
|
"%s AS id, %s AS available_qty, %s AS created_at",
|
||||||
|
cfg.Columns.ID,
|
||||||
|
fmt.Sprintf("%s - COALESCE(%s,0)", cfg.Columns.TotalQuantity, cfg.Columns.TotalUsedQuantity),
|
||||||
|
cfg.Columns.CreatedAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var rows []struct {
|
var rows []struct {
|
||||||
ID uint
|
ID uint
|
||||||
|
|||||||
@@ -2,16 +2,6 @@ package entities
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
const (
|
|
||||||
LogTypeAdjustment = "ADJUSTMENT"
|
|
||||||
LogTypeTransfer = "TRANSFER"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
TransactionTypeIncrease = "INCREASE"
|
|
||||||
TransactionTypeDecrease = "DECREASE"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StockLog struct {
|
type StockLog struct {
|
||||||
Id uint `gorm:"primaryKey;column:id"`
|
Id uint `gorm:"primaryKey;column:id"`
|
||||||
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null;index"`
|
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null;index"`
|
||||||
|
|||||||
@@ -783,7 +783,7 @@ func splitStockLogs(rows []stockLogSapronakRow, refFn func(stockLogSapronakRow)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) {
|
func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) {
|
||||||
rows, err := r.fetchStockLogs(ctx, kandangID, entity.LogTypeAdjustment, false)
|
rows, err := r.fetchStockLogs(ctx, kandangID, string(utils.StockLogTypeAdjustment), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -792,7 +792,7 @@ func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, ka
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) {
|
func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) {
|
||||||
rows, err := r.fetchStockLogs(ctx, kandangID, entity.LogTypeTransfer, true)
|
rows, err := r.fetchStockLogs(ctx, kandangID, string(utils.StockLogTypeTransfer), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.StockLog, err
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if stockLog.LoggableType != entity.LogTypeAdjustment {
|
if stockLog.LoggableType != string(utils.StockLogTypeAdjustment) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Adjustment not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Adjustment not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Quantity must be greater than zero")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Quantity must be greater than zero")
|
||||||
}
|
}
|
||||||
transactionType := strings.ToUpper(req.TransactionType)
|
transactionType := strings.ToUpper(req.TransactionType)
|
||||||
if transactionType != entity.TransactionTypeIncrease && transactionType != entity.TransactionTypeDecrease {
|
if transactionType != string(utils.StockLogTransactionTypeIncrease) && transactionType != string(utils.StockLogTransactionTypeDecrease) {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transaction type")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transaction type")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,14 +154,14 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
|
|
||||||
afterQuantity := productWarehouse.Quantity
|
afterQuantity := productWarehouse.Quantity
|
||||||
newLog := &entity.StockLog{
|
newLog := &entity.StockLog{
|
||||||
// TransactionType: transactionType,
|
|
||||||
LoggableType: entity.LogTypeAdjustment,
|
LoggableType: string(utils.StockLogTypeAdjustment),
|
||||||
LoggableId: 0,
|
LoggableId: 0,
|
||||||
Notes: req.Note,
|
Notes: req.Note,
|
||||||
ProductWarehouseId: productWarehouse.Id,
|
ProductWarehouseId: productWarehouse.Id,
|
||||||
CreatedBy: actorID, // TODO: should Get from auth middleware
|
CreatedBy: actorID, // TODO: should Get from auth middleware
|
||||||
}
|
}
|
||||||
if transactionType == entity.TransactionTypeIncrease {
|
if transactionType == string(utils.StockLogTransactionTypeIncrease) {
|
||||||
afterQuantity += req.Quantity
|
afterQuantity += req.Quantity
|
||||||
newLog.Increase = afterQuantity
|
newLog.Increase = afterQuantity
|
||||||
} else {
|
} else {
|
||||||
@@ -248,7 +248,7 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu
|
|||||||
|
|
||||||
db = s.withRelations(db)
|
db = s.withRelations(db)
|
||||||
|
|
||||||
db = db.Where("loggable_type = ?", entity.LogTypeAdjustment)
|
db = db.Where("loggable_type = ?", string(utils.StockLogTypeAdjustment))
|
||||||
|
|
||||||
if query.TransactionType != "" {
|
if query.TransactionType != "" {
|
||||||
db = db.Where("transaction_type = ?", strings.ToUpper(query.TransactionType))
|
db = db.Where("transaction_type = ?", strings.ToUpper(query.TransactionType))
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s.DocumentSvc != nil && len(files) > 0 {
|
if s.DocumentSvc != nil && len(files) > 0 {
|
||||||
// Upload documents for each delivery
|
|
||||||
for idx, file := range files {
|
for idx, file := range files {
|
||||||
documentFiles := []commonSvc.DocumentFile{
|
documentFiles := []commonSvc.DocumentFile{
|
||||||
{
|
{
|
||||||
@@ -296,7 +296,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
decreaseLog := &entity.StockLog{
|
decreaseLog := &entity.StockLog{
|
||||||
Decrease: product.ProductQty,
|
Decrease: product.ProductQty,
|
||||||
Notes: "",
|
Notes: "",
|
||||||
LoggableType: entity.LogTypeTransfer,
|
LoggableType: string(utils.StockLogTypeTransfer),
|
||||||
LoggableId: uint(entityTransfer.Id),
|
LoggableId: uint(entityTransfer.Id),
|
||||||
ProductWarehouseId: sourcePW.Id,
|
ProductWarehouseId: sourcePW.Id,
|
||||||
CreatedBy: actorID,
|
CreatedBy: actorID,
|
||||||
@@ -335,7 +335,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
|
|||||||
|
|
||||||
increaseLog := &entity.StockLog{
|
increaseLog := &entity.StockLog{
|
||||||
Increase: product.ProductQty,
|
Increase: product.ProductQty,
|
||||||
LoggableType: entity.LogTypeTransfer,
|
LoggableType: string(utils.StockLogTypeTransfer),
|
||||||
LoggableId: uint(entityTransfer.Id),
|
LoggableId: uint(entityTransfer.Id),
|
||||||
Notes: "",
|
Notes: "",
|
||||||
ProductWarehouseId: destPW.Id,
|
ProductWarehouseId: destPW.Id,
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
|||||||
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
|
fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log)
|
||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
|
||||||
// Register PROJECT_CHICKIN as usable
|
|
||||||
if err := fifoService.RegisterUsable(fifo.UsableConfig{
|
if err := fifoService.RegisterUsable(fifo.UsableConfig{
|
||||||
Key: fifo.UsableKeyProjectChickin,
|
Key: fifo.UsableKeyProjectChickin,
|
||||||
Table: "project_chickins",
|
Table: "project_chickins",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/validations"
|
||||||
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
|
rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ type chickinService struct {
|
|||||||
ProjectflockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository
|
ProjectflockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository
|
||||||
ProjectChickinDetailRepo repository.ProjectChickinDetailRepository
|
ProjectChickinDetailRepo repository.ProjectChickinDetailRepository
|
||||||
FifoSvc commonSvc.FifoService
|
FifoSvc commonSvc.FifoService
|
||||||
|
StockLogRepo rStockLogs.StockLogRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, projectflockkandangRepo rProjectFlock.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, projectChickinDetailRepo repository.ProjectChickinDetailRepository, validate *validator.Validate, fifoSvc commonSvc.FifoService) ChickinService {
|
func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, projectflockkandangRepo rProjectFlock.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, projectChickinDetailRepo repository.ProjectChickinDetailRepository, validate *validator.Validate, fifoSvc commonSvc.FifoService) ChickinService {
|
||||||
@@ -63,6 +65,7 @@ func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo Kan
|
|||||||
ProjectflockPopulationRepo: projectflockpopulationRepo,
|
ProjectflockPopulationRepo: projectflockpopulationRepo,
|
||||||
ProjectChickinDetailRepo: projectChickinDetailRepo,
|
ProjectChickinDetailRepo: projectChickinDetailRepo,
|
||||||
FifoSvc: fifoSvc,
|
FifoSvc: fifoSvc,
|
||||||
|
StockLogRepo: rStockLogs.NewStockLogRepository(repo.DB()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +138,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newChikins := make([]*entity.ProjectChickin, 0)
|
newChikins := make([]*entity.ProjectChickin, 0)
|
||||||
chickinQtyMap := make(map[uint]float64) // Store desired qty for each chickin index
|
chickinQtyMap := make(map[uint]float64)
|
||||||
|
|
||||||
for idx, chickinReq := range req.ChickinRequests {
|
for idx, chickinReq := range req.ChickinRequests {
|
||||||
|
|
||||||
@@ -197,13 +200,11 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti
|
|||||||
|
|
||||||
for idx, chickin := range newChikins {
|
for idx, chickin := range newChikins {
|
||||||
desiredQty := chickinQtyMap[uint(idx)]
|
desiredQty := chickinQtyMap[uint(idx)]
|
||||||
if err := s.ConsumeChickinStocks(c.Context(), dbTransaction, chickin, desiredQty); err != nil {
|
if err := s.ConsumeChickinStocks(c.Context(), dbTransaction, chickin, desiredQty, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: FIFO Consume already adjusts product warehouse quantities, no need to adjust again
|
|
||||||
|
|
||||||
latest, err := approvalSvcTx.LatestByTarget(c.Context(), utils.ApprovalWorkflowChickin, projectFlockKandang.Id, nil)
|
latest, err := approvalSvcTx.LatestByTarget(c.Context(), utils.ApprovalWorkflowChickin, projectFlockKandang.Id, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get latest approval")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get latest approval")
|
||||||
@@ -306,8 +307,13 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actorID, err := m.ActorIDFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if chickin.UsageQty > 0 {
|
if chickin.UsageQty > 0 {
|
||||||
if err := s.ReleaseChickinStocks(c.Context(), s.Repository.DB(), chickin); err != nil {
|
if err := s.ReleaseChickinStocks(c.Context(), s.Repository.DB(), chickin, actorID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,7 +467,7 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit
|
|||||||
|
|
||||||
for _, chickin := range chickins {
|
for _, chickin := range chickins {
|
||||||
|
|
||||||
if err := s.ReleaseChickinStocks(c.Context(), dbTransaction, &chickin); err != nil {
|
if err := s.ReleaseChickinStocks(c.Context(), dbTransaction, &chickin, actorID); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to release stock for rejected chickin %d: %v", chickin.Id, err))
|
return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to release stock for rejected chickin %d: %v", chickin.Id, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +597,7 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, desiredQty float64) error {
|
func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, desiredQty float64, actorID uint) error {
|
||||||
if chickin == nil || s.FifoSvc == nil {
|
if chickin == nil || s.FifoSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -622,14 +628,35 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if result.UsageQuantity > 0 {
|
||||||
|
decreaseLog := &entity.StockLog{
|
||||||
|
Decrease: result.UsageQuantity,
|
||||||
|
LoggableType: string(utils.StockLogTypeChikin),
|
||||||
|
LoggableId: chickin.Id,
|
||||||
|
ProductWarehouseId: chickin.ProductWarehouseId,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Notes: fmt.Sprintf("Chickin #%d", chickin.Id),
|
||||||
|
}
|
||||||
|
if err := s.StockLogRepo.CreateOne(ctx, decreaseLog, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create stock log for chickin %d: %+v", chickin.Id, err)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin) error {
|
func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, actorID uint) error {
|
||||||
if chickin == nil || s.FifoSvc == nil {
|
if chickin == nil || s.FifoSvc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var currentUsage float64
|
||||||
|
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Select("usage_qty").Scan(¤tUsage).Error; err != nil {
|
||||||
|
s.Log.Warnf("Failed to get current usage for chickin %d: %+v", chickin.Id, err)
|
||||||
|
currentUsage = 0
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{
|
||||||
UsableKey: chickinUsableKey,
|
UsableKey: chickinUsableKey,
|
||||||
UsableID: chickin.Id,
|
UsableID: chickin.Id,
|
||||||
@@ -646,6 +673,22 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create stock log for the restoration
|
||||||
|
if currentUsage > 0 {
|
||||||
|
increaseLog := &entity.StockLog{
|
||||||
|
Increase: currentUsage,
|
||||||
|
LoggableType: string(utils.StockLogTypeChikin),
|
||||||
|
LoggableId: chickin.Id,
|
||||||
|
ProductWarehouseId: chickin.ProductWarehouseId,
|
||||||
|
CreatedBy: actorID,
|
||||||
|
Notes: fmt.Sprintf("Chickin #%d - Stock released", chickin.Id),
|
||||||
|
}
|
||||||
|
if err := s.StockLogRepo.CreateOne(ctx, increaseLog, nil); err != nil {
|
||||||
|
s.Log.Errorf("Failed to create stock log for chickin %d: %+v", chickin.Id, err)
|
||||||
|
// Don't return error here, stock already released
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ type StockLogType string
|
|||||||
const (
|
const (
|
||||||
StockLogTypeAdjustment StockLogType = "ADJUSTMENT"
|
StockLogTypeAdjustment StockLogType = "ADJUSTMENT"
|
||||||
StockLogTypeTransfer StockLogType = "TRANSFER"
|
StockLogTypeTransfer StockLogType = "TRANSFER"
|
||||||
|
StockLogTypeMarketing StockLogType = "MARKETING"
|
||||||
|
StockLogTypeChikin StockLogType = "CHICKIN"
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user