mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
update purchase triger to expense
This commit is contained in:
+57
-58
@@ -3,7 +3,7 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
// "gitlab.com/mbugroup/lti-api.git/internal/config"
|
"gitlab.com/mbugroup/lti-api.git/internal/config"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/sso/session"
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
@@ -32,65 +32,65 @@ type AuthContext struct {
|
|||||||
// fine-grained authorization using the SSO access token scopes.
|
// fine-grained authorization using the SSO access token scopes.
|
||||||
func Auth(userService service.UserService, requiredScopes ...string) fiber.Handler {
|
func Auth(userService service.UserService, requiredScopes ...string) fiber.Handler {
|
||||||
return func(c *fiber.Ctx) error {
|
return func(c *fiber.Ctx) error {
|
||||||
// token := bearerToken(c)
|
token := bearerToken(c)
|
||||||
// if token == "" {
|
if token == "" {
|
||||||
// token = strings.TrimSpace(c.Cookies(config.SSOAccessCookieName))
|
token = strings.TrimSpace(c.Cookies(config.SSOAccessCookieName))
|
||||||
// }
|
}
|
||||||
// if token == "" {
|
if token == "" {
|
||||||
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
// }
|
}
|
||||||
|
|
||||||
// verification, err := sso.VerifyAccessToken(token)
|
verification, err := sso.VerifyAccessToken(token)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// utils.Log.WithError(err).Warn("auth: token verification failed")
|
utils.Log.WithError(err).Warn("auth: token verification failed")
|
||||||
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if verification.UserID == 0 {
|
if verification.UserID == 0 {
|
||||||
// return fiber.NewError(fiber.StatusForbidden, "Service authentication is not permitted for this endpoint")
|
return fiber.NewError(fiber.StatusForbidden, "Service authentication is not permitted for this endpoint")
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if err := ensureNotRevoked(c, token, verification); err != nil {
|
if err := ensureNotRevoked(c, token, verification); err != nil {
|
||||||
// return err
|
return err
|
||||||
// }
|
}
|
||||||
|
|
||||||
// user, err := userService.GetBySSOUserID(c, verification.UserID)
|
user, err := userService.GetBySSOUserID(c, verification.UserID)
|
||||||
// if err != nil || user == nil {
|
if err != nil || user == nil {
|
||||||
// utils.Log.WithError(err).Warn("auth: failed to resolve user from repository")
|
utils.Log.WithError(err).Warn("auth: failed to resolve user from repository")
|
||||||
// return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
return fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if len(requiredScopes) > 0 {
|
if len(requiredScopes) > 0 {
|
||||||
// if verification.Claims == nil || !hasAllScopes(verification.Claims.Scopes(), requiredScopes) {
|
if verification.Claims == nil || !hasAllScopes(verification.Claims.Scopes(), requiredScopes) {
|
||||||
// return fiber.NewError(fiber.StatusForbidden, "Insufficient scope")
|
return fiber.NewError(fiber.StatusForbidden, "Insufficient scope")
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// var roles []sso.Role
|
var roles []sso.Role
|
||||||
// permissions := make(map[string]struct{})
|
permissions := make(map[string]struct{})
|
||||||
// if verification.UserID != 0 {
|
if verification.UserID != 0 {
|
||||||
// if profile, err := sso.FetchProfile(c.Context(), token, verification); err != nil {
|
if profile, err := sso.FetchProfile(c.Context(), token, verification); err != nil {
|
||||||
// utils.Log.WithError(err).Warn("auth: failed to fetch sso profile")
|
utils.Log.WithError(err).Warn("auth: failed to fetch sso profile")
|
||||||
// } else if profile != nil {
|
} else if profile != nil {
|
||||||
// roles = profile.Roles
|
roles = profile.Roles
|
||||||
// for _, perm := range profile.PermissionNames() {
|
for _, perm := range profile.PermissionNames() {
|
||||||
// if perm != "" {
|
if perm != "" {
|
||||||
// permissions[perm] = struct{}{}
|
permissions[perm] = struct{}{}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// ctx := &AuthContext{
|
ctx := &AuthContext{
|
||||||
// Token: token,
|
Token: token,
|
||||||
// Verification: verification,
|
Verification: verification,
|
||||||
// User: user,
|
User: user,
|
||||||
// Roles: roles,
|
Roles: roles,
|
||||||
// Permissions: permissions,
|
Permissions: permissions,
|
||||||
// }
|
}
|
||||||
|
|
||||||
// c.Locals(authContextLocalsKey, ctx)
|
c.Locals(authContextLocalsKey, ctx)
|
||||||
// c.Locals(authUserLocalsKey, user)
|
c.Locals(authUserLocalsKey, user)
|
||||||
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
@@ -106,12 +106,11 @@ func AuthenticatedUser(c *fiber.Ctx) (*entity.User, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ActorIDFromContext(c *fiber.Ctx) (uint, error) {
|
func ActorIDFromContext(c *fiber.Ctx) (uint, error) {
|
||||||
// user, ok := AuthenticatedUser(c)
|
user, ok := AuthenticatedUser(c)
|
||||||
// if !ok || user == nil || user.Id == 0 {
|
if !ok || user == nil || user.Id == 0 {
|
||||||
// return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate")
|
||||||
// }
|
}
|
||||||
// return user.Id, nil
|
return user.Id, nil
|
||||||
return 1, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthDetails returns the full authentication context (token, claims, user).
|
// AuthDetails returns the full authentication context (token, claims, user).
|
||||||
|
|||||||
@@ -22,13 +22,11 @@ import (
|
|||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PurchaseExpenseBridge allows purchase flows to sync expense data on receiving/deletion.
|
|
||||||
type PurchaseExpenseBridge interface {
|
type PurchaseExpenseBridge interface {
|
||||||
OnItemsDeleted(ctx context.Context, purchaseID uint, items []entity.PurchaseItem) error
|
OnItemsDeleted(ctx context.Context, purchaseID uint, items []entity.PurchaseItem) error
|
||||||
OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates []ExpenseReceivingPayload) error
|
OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates []ExpenseReceivingPayload) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpenseReceivingPayload captures the minimum data expense integration will need once available.
|
|
||||||
type ExpenseReceivingPayload struct {
|
type ExpenseReceivingPayload struct {
|
||||||
PurchaseItemID uint
|
PurchaseItemID uint
|
||||||
ProductID uint
|
ProductID uint
|
||||||
@@ -51,7 +49,6 @@ func groupingKey(supplierID uint, date time.Time, warehouseID uint) string {
|
|||||||
return fmt.Sprintf("%d:%s:%d", supplierID, utils.FormatDate(date), warehouseID)
|
return fmt.Sprintf("%d:%s:%d", supplierID, utils.FormatDate(date), warehouseID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// expenseBridge is the real implementation that syncs purchases to expenses on receiving/deletion.
|
|
||||||
type expenseBridge struct {
|
type expenseBridge struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
purchaseRepo rPurchase.PurchaseRepository
|
purchaseRepo rPurchase.PurchaseRepository
|
||||||
@@ -158,7 +155,6 @@ func (b *expenseBridge) OnItemsDeleted(ctx context.Context, _ uint, items []enti
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanupExistingNonstocks deletes expense_nonstocks (and their approvals/expenses if empty) for the given payloads.
|
|
||||||
func (b *expenseBridge) cleanupExistingNonstocks(ctx context.Context, updates []ExpenseReceivingPayload) error {
|
func (b *expenseBridge) cleanupExistingNonstocks(ctx context.Context, updates []ExpenseReceivingPayload) error {
|
||||||
if len(updates) == 0 {
|
if len(updates) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -236,7 +232,6 @@ func (b *expenseBridge) cleanupExistingNonstocks(ctx context.Context, updates []
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanupEmptyExpenses deletes expense headers (and approvals) that have no nonstocks.
|
|
||||||
func (b *expenseBridge) cleanupEmptyExpenses(ctx context.Context, expenseIDs []uint64) error {
|
func (b *expenseBridge) cleanupEmptyExpenses(ctx context.Context, expenseIDs []uint64) error {
|
||||||
if len(expenseIDs) == 0 {
|
if len(expenseIDs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -263,6 +258,23 @@ func (b *expenseBridge) cleanupEmptyExpenses(ctx context.Context, expenseIDs []u
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *expenseBridge) markExpensesUpdated(ctx context.Context, expenseIDs map[uint64]struct{}, actorID uint) error {
|
||||||
|
if len(expenseIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if actorID == 0 {
|
||||||
|
actorID = 1
|
||||||
|
}
|
||||||
|
svc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(b.db))
|
||||||
|
action := entity.ApprovalActionUpdated
|
||||||
|
for id := range expenseIDs {
|
||||||
|
if _, err := svc.CreateApproval(ctx, utils.ApprovalWorkflowExpense, uint(id), utils.ExpenseStepFinance, &action, actorID, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates []ExpenseReceivingPayload) error {
|
func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates []ExpenseReceivingPayload) error {
|
||||||
if purchaseID == 0 || len(updates) == 0 {
|
if purchaseID == 0 || len(updates) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -292,6 +304,7 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
|||||||
|
|
||||||
itemLinks := make(map[uint]itemLink)
|
itemLinks := make(map[uint]itemLink)
|
||||||
existingExpenseByKey := make(map[string]uint64)
|
existingExpenseByKey := make(map[string]uint64)
|
||||||
|
updatedExpenses := make(map[uint64]struct{})
|
||||||
if len(updates) > 0 {
|
if len(updates) > 0 {
|
||||||
ids := make([]uint, 0, len(updates))
|
ids := make([]uint, 0, len(updates))
|
||||||
for _, upd := range updates {
|
for _, upd := range updates {
|
||||||
@@ -407,6 +420,7 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
|||||||
// Track cleanup for old header if it becomes empty.
|
// Track cleanup for old header if it becomes empty.
|
||||||
movedFrom = append(movedFrom, link.ExpenseID)
|
movedFrom = append(movedFrom, link.ExpenseID)
|
||||||
existingExpenseByKey[newKey] = targetExpenseID
|
existingExpenseByKey[newKey] = targetExpenseID
|
||||||
|
updatedExpenses[targetExpenseID] = struct{}{}
|
||||||
handledUpdate = true
|
handledUpdate = true
|
||||||
} else {
|
} else {
|
||||||
requiresDelete = true
|
requiresDelete = true
|
||||||
@@ -426,6 +440,9 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
|||||||
}).Error; err != nil {
|
}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if link.ExpenseID != 0 {
|
||||||
|
updatedExpenses[link.ExpenseID] = struct{}{}
|
||||||
|
}
|
||||||
handledUpdate = true
|
handledUpdate = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,6 +481,9 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
|||||||
kandangID: kandangID,
|
kandangID: kandangID,
|
||||||
totalPrice: totalPrice,
|
totalPrice: totalPrice,
|
||||||
})
|
})
|
||||||
|
if existingID, ok := existingExpenseByKey[key]; ok && existingID != 0 {
|
||||||
|
updatedExpenses[existingID] = struct{}{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For payloads that require delete/recreate, clean up their old links first.
|
// For payloads that require delete/recreate, clean up their old links first.
|
||||||
@@ -541,6 +561,9 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
|||||||
if err := b.linkExpenseNonstocksToItems(ctx, expenseDetail, items); err != nil {
|
if err := b.linkExpenseNonstocksToItems(ctx, expenseDetail, items); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if expenseDetail != nil && expenseDetail.Id != 0 {
|
||||||
|
updatedExpenses[uint64(expenseDetail.Id)] = struct{}{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup old expense headers that became empty after re-link.
|
// Cleanup old expense headers that became empty after re-link.
|
||||||
@@ -550,6 +573,12 @@ func (b *expenseBridge) OnItemsReceived(c *fiber.Ctx, purchaseID uint, updates [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(updatedExpenses) > 0 {
|
||||||
|
if err := b.markExpensesUpdated(ctx, updatedExpenses, purchase.CreatedBy); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -996,10 +996,8 @@ func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint) error {
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get purchase")
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get purchase")
|
||||||
}
|
}
|
||||||
|
|
||||||
itemIDs := make([]uint, 0, len(purchase.Items))
|
|
||||||
itemsToDelete := make([]entity.PurchaseItem, len(purchase.Items))
|
itemsToDelete := make([]entity.PurchaseItem, len(purchase.Items))
|
||||||
for i, item := range purchase.Items {
|
for i, item := range purchase.Items {
|
||||||
itemIDs = append(itemIDs, item.Id)
|
|
||||||
itemsToDelete[i] = item
|
itemsToDelete[i] = item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user