Add tender submission functionality and related routes
- Introduced the `tender_submission` domain, including entity, repository, service, and handler implementations for managing tender submissions. - Added new routes for both admin and public access to tender submissions, allowing for listing, ensuring, and retrieving submissions by ID and tender. - Enhanced the tender approval service to synchronize submission workflows with approval changes, ensuring proper state management. - Implemented validation and response structures for tender submission operations, improving API consistency and usability. - Added unit tests for tender submission status transitions and workflow logic, ensuring robust functionality. This update enhances the tender management system by providing comprehensive support for tender submissions, improving overall workflow and user experience.
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package tender_submission
|
||||
|
||||
import (
|
||||
"time"
|
||||
"tm/pkg/mongo"
|
||||
)
|
||||
|
||||
// SubmissionStatus represents the current step in the tender submission workflow.
|
||||
type SubmissionStatus string
|
||||
|
||||
const (
|
||||
StatusOpportunity SubmissionStatus = "opportunity"
|
||||
StatusValidating SubmissionStatus = "validating"
|
||||
StatusPartnerPending SubmissionStatus = "partner_pending"
|
||||
StatusApplying SubmissionStatus = "applying"
|
||||
StatusSentWaiting SubmissionStatus = "sent_waiting"
|
||||
StatusRejected SubmissionStatus = "rejected"
|
||||
StatusContract SubmissionStatus = "contract"
|
||||
StatusNotApplied SubmissionStatus = "not_applied"
|
||||
)
|
||||
|
||||
// SubmissionStage groups statuses into high-level workflow stages for UI display.
|
||||
type SubmissionStage string
|
||||
|
||||
const (
|
||||
StageOpportunity SubmissionStage = "opportunity"
|
||||
StageInProgress SubmissionStage = "in_progress"
|
||||
StageSubmitted SubmissionStage = "submitted"
|
||||
StageNotApplied SubmissionStage = "not_applied"
|
||||
)
|
||||
|
||||
// StatusHistory records a status change in the submission workflow.
|
||||
type StatusHistory struct {
|
||||
Status SubmissionStatus `bson:"status" json:"status"`
|
||||
Reason string `bson:"reason,omitempty" json:"reason,omitempty"`
|
||||
ChangedBy string `bson:"changed_by" json:"changed_by"`
|
||||
ChangedAt int64 `bson:"changed_at" json:"changed_at"`
|
||||
Description string `bson:"description,omitempty" json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// TenderSubmission tracks a company's tender submission workflow.
|
||||
type TenderSubmission struct {
|
||||
mongo.Model `bson:",inline"`
|
||||
|
||||
TenderID string `bson:"tender_id" json:"tender_id"`
|
||||
CompanyID string `bson:"company_id" json:"company_id"`
|
||||
CustomerID string `bson:"customer_id,omitempty" json:"customer_id,omitempty"`
|
||||
Status SubmissionStatus `bson:"status" json:"status"`
|
||||
Stage SubmissionStage `bson:"stage" json:"stage"`
|
||||
History []StatusHistory `bson:"status_history" json:"status_history"`
|
||||
}
|
||||
|
||||
func StageForStatus(status SubmissionStatus) SubmissionStage {
|
||||
switch status {
|
||||
case StatusOpportunity:
|
||||
return StageOpportunity
|
||||
case StatusValidating, StatusPartnerPending, StatusApplying:
|
||||
return StageInProgress
|
||||
case StatusSentWaiting, StatusRejected, StatusContract:
|
||||
return StageSubmitted
|
||||
case StatusNotApplied:
|
||||
return StageNotApplied
|
||||
default:
|
||||
return StageOpportunity
|
||||
}
|
||||
}
|
||||
|
||||
func IsTerminalStatus(status SubmissionStatus) bool {
|
||||
switch status {
|
||||
case StatusRejected, StatusContract, StatusNotApplied:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func AllowedNextStatuses(current SubmissionStatus) []SubmissionStatus {
|
||||
transitions := map[SubmissionStatus][]SubmissionStatus{
|
||||
StatusOpportunity: {StatusValidating, StatusNotApplied},
|
||||
StatusValidating: {StatusPartnerPending, StatusApplying, StatusNotApplied},
|
||||
StatusPartnerPending: {StatusApplying, StatusNotApplied},
|
||||
StatusApplying: {StatusSentWaiting, StatusNotApplied},
|
||||
StatusSentWaiting: {StatusRejected, StatusContract},
|
||||
StatusRejected: {},
|
||||
StatusContract: {},
|
||||
StatusNotApplied: {},
|
||||
}
|
||||
return transitions[current]
|
||||
}
|
||||
|
||||
func (ts *TenderSubmission) GetID() string {
|
||||
return ts.ID.Hex()
|
||||
}
|
||||
|
||||
func (ts *TenderSubmission) SetCreatedAt(timestamp int64) {
|
||||
ts.CreatedAt = timestamp
|
||||
}
|
||||
|
||||
func (ts *TenderSubmission) SetUpdatedAt(timestamp int64) {
|
||||
ts.UpdatedAt = timestamp
|
||||
}
|
||||
|
||||
func (ts *TenderSubmission) AddStatusChange(status SubmissionStatus, reason, changedBy, description string) {
|
||||
ts.History = append(ts.History, StatusHistory{
|
||||
Status: status,
|
||||
Reason: reason,
|
||||
ChangedBy: changedBy,
|
||||
ChangedAt: time.Now().Unix(),
|
||||
Description: description,
|
||||
})
|
||||
ts.Status = status
|
||||
ts.Stage = StageForStatus(status)
|
||||
ts.SetUpdatedAt(time.Now().Unix())
|
||||
}
|
||||
|
||||
func NewTenderSubmission(tenderID, companyID, customerID string) *TenderSubmission {
|
||||
now := time.Now().Unix()
|
||||
ts := &TenderSubmission{
|
||||
TenderID: tenderID,
|
||||
CompanyID: companyID,
|
||||
CustomerID: customerID,
|
||||
Status: StatusOpportunity,
|
||||
Stage: StageOpportunity,
|
||||
}
|
||||
ts.SetCreatedAt(now)
|
||||
ts.SetUpdatedAt(now)
|
||||
ts.AddStatusChange(StatusOpportunity, "", customerID, "Submission workflow started")
|
||||
return ts
|
||||
}
|
||||
|
||||
func (ts *TenderSubmission) ToResponse() *TenderSubmissionResponse {
|
||||
history := make([]StatusHistoryResponse, len(ts.History))
|
||||
for i, h := range ts.History {
|
||||
history[i] = StatusHistoryResponse{
|
||||
Status: string(h.Status),
|
||||
Reason: h.Reason,
|
||||
ChangedBy: h.ChangedBy,
|
||||
ChangedAt: h.ChangedAt,
|
||||
Description: h.Description,
|
||||
}
|
||||
}
|
||||
|
||||
return &TenderSubmissionResponse{
|
||||
ID: ts.GetID(),
|
||||
TenderID: ts.TenderID,
|
||||
CompanyID: ts.CompanyID,
|
||||
CustomerID: ts.CustomerID,
|
||||
Status: string(ts.Status),
|
||||
Stage: string(ts.Stage),
|
||||
History: history,
|
||||
CreatedAt: ts.CreatedAt,
|
||||
UpdatedAt: ts.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *TenderSubmission) ToResponseWithTender(tender *TenderDetails) *TenderSubmissionWithTenderResponse {
|
||||
resp := ts.ToResponse()
|
||||
return &TenderSubmissionWithTenderResponse{
|
||||
TenderSubmissionResponse: *resp,
|
||||
Tender: tender,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user