diff --git a/src/app/features/quotes/pages/quote-list/quote-list.component.ts b/src/app/features/quotes/pages/quote-list/quote-list.component.ts index 34db129b..91c0f28e 100644 --- a/src/app/features/quotes/pages/quote-list/quote-list.component.ts +++ b/src/app/features/quotes/pages/quote-list/quote-list.component.ts @@ -16,6 +16,7 @@ import { QuoteDetailsModalComponent } from 'src/app/shared/quote-details-modal/q import { ChatModalComponent } from 'src/app/shared/chat-modal/chat-modal.component'; import { AttachmentModalComponent } from 'src/app/shared/attachment-modal/attachment-modal.component'; import { LoginInfo } from 'src/app/models/interfaces'; +import { QUOTE_STATUSES, TAILORED_STATUSES_LABELS_CUSTOMER, TAILORED_STATUSES_LABELS_PROVIDER } from 'src/app/models/quote.constants'; import { environment } from 'src/environments/environment'; @Component({ @@ -91,11 +92,11 @@ import { environment } from 'src/environments/environment'; class="form-select rounded-md border-gray-300 dark:bg-gray-700 dark:border-gray-800 dark:text-white shadow-sm focus:border-indigo-500 focus:ring-indigo-500" > - - - - - + + + + + @@ -169,7 +170,7 @@ import { environment } from 'src/environments/environment';
- {{ getPrimaryState(quote) }} + {{ getStatusLabel(quote) }}
@@ -412,7 +413,17 @@ export class QuoteListComponent implements OnInit { showStateUpdate = false; quoteToUpdate: Quote | null = null; selectedState: QuoteStateType | null = null; - availableStates: QuoteStateType[] = ['pending', 'inProgress', 'approved', 'rejected', 'cancelled', 'accepted']; + availableStates: QuoteStateType[] = [ + QUOTE_STATUSES.PENDING, + QUOTE_STATUSES.IN_PROGRESS, + QUOTE_STATUSES.APPROVED, + QUOTE_STATUSES.REJECTED, + QUOTE_STATUSES.CANCELLED, + QUOTE_STATUSES.ACCEPTED + ]; + + // Expose constants to template + readonly QUOTE_STATUSES = QUOTE_STATUSES; // Role management selectedRole: 'customer' | 'seller' = 'customer'; @@ -469,13 +480,6 @@ export class QuoteListComponent implements OnInit { next: (quotes) => { this.quotes = quotes; - // Debug: Log quote states and product info - console.log('Loaded quotes:', quotes.length); - quotes.forEach(quote => { - console.log(`Quote ${this.extractShortId(quote.id)}: main state = "${quote.state}", primary state = "${this.getPrimaryState(quote)}"`); - const productOffering = quote.quoteItem?.[0]?.productOffering; - console.log(` - productOffering:`, productOffering); - }); // Enrich quote data with organization and product names this.enrichQuoteData(quotes); @@ -653,10 +657,10 @@ export class QuoteListComponent implements OnInit { // If the current user is a provider (seller) and the quote is in progress, // automatically update the status to 'approved' after successful PDF upload - if (this.selectedRole === 'seller' && this.getPrimaryState(updatedQuote) === 'inProgress') { + if (this.selectedRole === 'seller' && this.getPrimaryState(updatedQuote) === QUOTE_STATUSES.IN_PROGRESS) { console.log('Provider uploaded PDF, updating quote status to approved:', updatedQuote.id); - - this.quoteService.updateQuoteStatus(updatedQuote.id!, 'approved').subscribe({ + + this.quoteService.updateQuoteStatus(updatedQuote.id!, QUOTE_STATUSES.APPROVED).subscribe({ next: (approvedQuote) => { // Update the quote again with the new status const approvedIndex = this.quotes.findIndex(q => q.id === approvedQuote.id); @@ -761,7 +765,7 @@ export class QuoteListComponent implements OnInit { this.acceptConfirmCallback = () => { console.log('Accepting quote request:', quote.id); - this.quoteService.updateQuoteStatus(quote.id!, 'inProgress').subscribe({ + this.quoteService.updateQuoteStatus(quote.id!, QUOTE_STATUSES.IN_PROGRESS).subscribe({ next: (updatedQuote) => { const index = this.quotes.findIndex(q => q.id === updatedQuote.id); if (index !== -1) { @@ -787,7 +791,7 @@ export class QuoteListComponent implements OnInit { this.acceptConfirmCallback = () => { console.log('Customer accepting quotation:', quote.id); - this.quoteService.updateQuoteStatus(quote.id!, 'accepted').subscribe({ + this.quoteService.updateQuoteStatus(quote.id!, QUOTE_STATUSES.ACCEPTED).subscribe({ next: (updatedQuote) => { const index = this.quotes.findIndex(q => q.id === updatedQuote.id); if (index !== -1) { @@ -881,7 +885,7 @@ export class QuoteListComponent implements OnInit { this.cancelConfirmCallback = () => { console.log('Cancelling quote:', quote.id); - this.quoteService.updateQuoteStatus(quote.id!, 'cancelled').subscribe({ + this.quoteService.updateQuoteStatus(quote.id!, QUOTE_STATUSES.CANCELLED).subscribe({ next: (updatedQuote) => { const index = this.quotes.findIndex(q => q.id === updatedQuote.id); if (index !== -1) { @@ -989,38 +993,58 @@ export class QuoteListComponent implements OnInit { if (Array.isArray(quote.quoteItem) && quote.quoteItem.length > 0) { return quote.quoteItem[0].state || 'unknown'; } - + // Fallback to main quote state if quoteItem state is not available if (quote.state) { return quote.state; } - + return 'unknown'; } + /** + * Get user-friendly status label for a quote based on role + */ + getStatusLabel(quote: Quote): string { + const state = this.getPrimaryState(quote); + + // Use appropriate label based on user role + const labels = this.selectedRole === 'customer' + ? TAILORED_STATUSES_LABELS_CUSTOMER + : TAILORED_STATUSES_LABELS_PROVIDER; + + // Map status to label + switch (state) { + case QUOTE_STATUSES.PENDING: + return labels.PENDING; + case QUOTE_STATUSES.IN_PROGRESS: + return labels.IN_PROGRESS; + case QUOTE_STATUSES.APPROVED: + return labels.APPROVED; + case QUOTE_STATUSES.ACCEPTED: + return labels.ACCEPTED; + case QUOTE_STATUSES.CANCELLED: + return labels.CANCELLED; + case QUOTE_STATUSES.REJECTED: + return labels.REJECTED; + default: + return state; + } + } + hasAttachment(quote: Quote): boolean { return Array.isArray(quote.quoteItem) && quote.quoteItem.some(qi => qi.attachment && qi.attachment.length > 0); } isQuoteCancelled(quote: Quote): boolean { - // Check quoteItem state first (this is where the actual state is stored) - if (quote.quoteItem?.some(item => item.state === 'cancelled')) { - return true; - } - - // Fallback to main quote state - return quote.state === 'cancelled'; + // Only check quoteItem state (this is the source of truth) + return quote.quoteItem?.some(item => item.state === QUOTE_STATUSES.CANCELLED) || false; } isQuoteAccepted(quote: Quote): boolean { - // Check quoteItem state first (this is where the actual state is stored) - if (quote.quoteItem?.some(item => item.state === 'accepted')) { - return true; - } - - // Fallback to main quote state - return quote.state === 'accepted'; + // Only check quoteItem state (this is the source of truth) + return quote.quoteItem?.some(item => item.state === QUOTE_STATUSES.ACCEPTED) || false; } isQuoteFinalized(quote: Quote): boolean { @@ -1093,32 +1117,32 @@ export class QuoteListComponent implements OnInit { getStateDisplay(state: QuoteStateType | undefined): string { if (!state) return 'Unknown'; - + const stateMap: Record = { - 'pending': 'Pending', - 'inProgress': 'In Progress', - 'approved': 'Approved', - 'rejected': 'Rejected', - 'cancelled': 'Cancelled', - 'accepted': 'Accepted' + [QUOTE_STATUSES.PENDING]: 'Pending', + [QUOTE_STATUSES.IN_PROGRESS]: 'In Progress', + [QUOTE_STATUSES.APPROVED]: 'Approved', + [QUOTE_STATUSES.REJECTED]: 'Rejected', + [QUOTE_STATUSES.CANCELLED]: 'Cancelled', + [QUOTE_STATUSES.ACCEPTED]: 'Accepted' }; - + return stateMap[state] || state; } getStateClass(state: string): string { switch (state) { - case 'pending': + case QUOTE_STATUSES.PENDING: return 'status-pending'; - case 'inProgress': + case QUOTE_STATUSES.IN_PROGRESS: return 'status-inProgress'; - case 'approved': + case QUOTE_STATUSES.APPROVED: return 'status-approved'; - case 'rejected': + case QUOTE_STATUSES.REJECTED: return 'status-rejected'; - case 'cancelled': + case QUOTE_STATUSES.CANCELLED: return 'status-cancelled'; - case 'accepted': + case QUOTE_STATUSES.ACCEPTED: return 'status-accepted'; default: return 'status-unknown'; @@ -1126,6 +1150,6 @@ export class QuoteListComponent implements OnInit { } canUpdateState(state: QuoteStateType | undefined): boolean { - return state !== 'cancelled' && state !== 'accepted'; + return state !== QUOTE_STATUSES.CANCELLED && state !== QUOTE_STATUSES.ACCEPTED; } } \ No newline at end of file diff --git a/src/app/features/quotes/services/quote.service.ts b/src/app/features/quotes/services/quote.service.ts index 3a2eb091..b6263b41 100644 --- a/src/app/features/quotes/services/quote.service.ts +++ b/src/app/features/quotes/services/quote.service.ts @@ -4,6 +4,7 @@ import { Observable, map, forkJoin } from 'rxjs'; import { Quote, Quote_Create, Quote_Update, QuoteStateType } from 'src/app/models/quote.model'; import { Tender } from 'src/app/models/tender.model'; import { ApiRole, API_ROLES } from 'src/app/models/roles.constants'; +import { QUOTE_STATUSES, QUOTE_CATEGORIES } from 'src/app/models/quote.constants'; import { environment } from '../../../../environments/environment'; @Injectable({ @@ -365,11 +366,11 @@ export class QuoteService { // Helper methods for quote status checking isQuoteCancelled(quote: Quote): boolean { - return quote.quoteItem?.some(item => item.state === 'cancelled') || false; + return quote.quoteItem?.some(item => item.state === QUOTE_STATUSES.CANCELLED) || false; } isQuoteAccepted(quote: Quote): boolean { - return quote.quoteItem?.some(item => item.state === 'accepted') || false; + return quote.quoteItem?.some(item => item.state === QUOTE_STATUSES.ACCEPTED) || false; } isQuoteFinalized(quote: Quote): boolean { @@ -556,9 +557,9 @@ export class QuoteService { // Map quote category to tender category let category: 'coordinator' | 'tendering' = 'coordinator'; - if (quote.category === 'tender') { + if (quote.category === QUOTE_CATEGORIES.TENDER) { category = 'tendering'; - } else if (quote.category === 'coordinator') { + } else if (quote.category === QUOTE_CATEGORIES.COORDINATOR) { category = 'coordinator'; } @@ -578,18 +579,21 @@ export class QuoteService { // - approved → sent → 'launched' // - accepted/cancelled/rejected → closed → 'closed' let state: 'draft' | 'pre-launched' | 'pending' | 'sent' | 'closed' = 'draft'; - if (quoteItemState === 'pending') state = 'draft'; - else if (quoteItemState === 'inProgress') state = 'pre-launched'; - else if (quoteItemState === 'approved') state = 'sent'; - else if (quoteItemState === 'accepted') state = 'closed'; - else if (quoteItemState === 'cancelled') state = 'closed'; - else if (quoteItemState === 'rejected') state = 'closed'; - - // Extract external_id and provider from quote + if (quoteItemState === QUOTE_STATUSES.PENDING) state = 'draft'; + else if (quoteItemState === QUOTE_STATUSES.IN_PROGRESS) state = 'pre-launched'; + else if (quoteItemState === QUOTE_STATUSES.APPROVED) state = 'sent'; + else if (quoteItemState === QUOTE_STATUSES.ACCEPTED) state = 'closed'; + else if (quoteItemState === QUOTE_STATUSES.CANCELLED) state = 'closed'; + else if (quoteItemState === QUOTE_STATUSES.REJECTED) state = 'closed'; + + // Extract external_id, provider and buyerPartyId from quote const external_id = quote.externalId; const provider = quote.relatedParty ?.find(party => party.role?.toLowerCase() === API_ROLES.SELLER.toLowerCase()) ?.name; + const buyerPartyId = quote.relatedParty + ?.find(party => party.role?.toLowerCase() === API_ROLES.BUYER.toLowerCase()) + ?.id; return { id: quote.id, @@ -601,6 +605,7 @@ export class QuoteService { selectedProviders, external_id, provider, + buyerPartyId, createdAt: quote.quoteDate, updatedAt: quote.quoteDate, effectiveQuoteCompletionDate: quote.effectiveQuoteCompletionDate, diff --git a/src/app/features/tenders/pages/tender-list/tender-list.component.ts b/src/app/features/tenders/pages/tender-list/tender-list.component.ts index fb34824a..c7d34be7 100644 --- a/src/app/features/tenders/pages/tender-list/tender-list.component.ts +++ b/src/app/features/tenders/pages/tender-list/tender-list.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, inject } from '@angular/core'; +import { Component, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; @@ -7,6 +7,7 @@ import { switchMap } from 'rxjs/operators'; import { QuoteService } from '../../../quotes/services/quote.service'; import { LocalStorageService } from 'src/app/services/local-storage.service'; import { NotificationService } from 'src/app/services/notification.service'; +import { AccountServiceService } from 'src/app/services/account-service.service'; import { Tender, TenderAttachment } from 'src/app/models/tender.model'; import { Quote, QuoteStateType } from 'src/app/models/quote.model'; import { LoginInfo } from 'src/app/models/interfaces'; @@ -17,24 +18,24 @@ import { ChatModalComponent } from 'src/app/shared/chat-modal/chat-modal.compone import { AttachmentModalComponent } from 'src/app/shared/attachment-modal/attachment-modal.component'; import { CreateTenderModalComponent } from 'src/app/shared/create-tender-modal/create-tender-modal.component'; import { UI_ROLES, API_ROLES, UiRole, toApiRole } from 'src/app/models/roles.constants'; -import { QUOTE_CATEGORIES, TENDER_CATEGORIES } from 'src/app/models/quote.constants'; +import { QUOTE_CATEGORIES, QUOTE_STATUSES, TENDER_COORDINATOR_STATUSES_LABELS, TENDER_RELATED_QUOTES_LABELS_CUSTOMER, TENDER_RELATED_QUOTES_LABELS_PROVIDER } from 'src/app/models/quote.constants'; @Component({ selector: 'app-quote-list', standalone: true, imports: [ - CommonModule, - FormsModule, - NotificationComponent, - ConfirmDialogComponent, - QuoteDetailsModalComponent, - ChatModalComponent, + CommonModule, + FormsModule, + NotificationComponent, + ConfirmDialogComponent, + QuoteDetailsModalComponent, + ChatModalComponent, AttachmentModalComponent, CreateTenderModalComponent ], template: ` - +
@@ -120,12 +121,12 @@ import { QUOTE_CATEGORIES, TENDER_CATEGORIES } from 'src/app/models/quote.consta
- +
- +
@@ -140,7 +141,7 @@ import { QUOTE_CATEGORIES, TENDER_CATEGORIES } from 'src/app/models/quote.consta
- +
@@ -150,7 +151,7 @@ import { QUOTE_CATEGORIES, TENDER_CATEGORIES } from 'src/app/models/quote.consta

No tenders found

No tender requests to display

- +
@@ -174,7 +175,7 @@ import { QUOTE_CATEGORIES, TENDER_CATEGORIES } from 'src/app/models/quote.consta
ACTIONS
- +
@@ -204,7 +205,7 @@ import { QUOTE_CATEGORIES, TENDER_CATEGORIES } from 'src/app/models/quote.consta
- {{ getQuoteItemState(quote) }} + {{ getStatusLabel(quote) }}
@@ -274,7 +275,7 @@ import { QUOTE_CATEGORIES, TENDER_CATEGORIES } from 'src/app/models/quote.consta
- {{ getQuoteItemState(quote) }} + {{ getStatusLabel(quote) }}
@@ -340,7 +341,7 @@ import { QUOTE_CATEGORIES, TENDER_CATEGORIES } from 'src/app/models/quote.consta
- +
@@ -496,11 +497,11 @@ import { QUOTE_CATEGORIES, TENDER_CATEGORIES } from 'src/app/models/quote.consta

Update Quote State

-