|
|
@@ -1,504 +0,0 @@
|
|
|
-import { Store } from "@ngxs/store";
|
|
|
-import { TMService } from "tm-ui/tm.service";
|
|
|
-import { Controller } from "fis-commons/src/controller/controller";
|
|
|
-import { Field, Fields } from "fis-commons/src/ui/field/field.i";
|
|
|
-import documentSchema from '../qt.document.schema.json';
|
|
|
-import { BehaviorSubject, MonoTypeOperatorFunction, Observable, Subject, concat, firstValueFrom, lastValueFrom, map, skip, take, takeWhile } from "rxjs";
|
|
|
-import { Document } from "fis-commons/src/tm/document/tm.document.i";
|
|
|
-import { Table, UITable } from "angularlib/table/table.i";
|
|
|
-import { Event } from "fis-commons/src/event/event.i";
|
|
|
-import { TreeTableEvent } from "angularlib/table/tree.table/tree.table.e";
|
|
|
-import { Ops } from "dp-ui/message.interface";
|
|
|
-import { ComponentService } from "angularlib/component.service";
|
|
|
-import { ServiceID } from "../serviceid";
|
|
|
-import { Process } from "fis-commons/process";
|
|
|
-
|
|
|
-/**operator to decide whether to use cahced or live responses
|
|
|
- * @param skipCached boolean, if undefined, default value is false, which returns cached data
|
|
|
-*/
|
|
|
-const skipCache = (skipCached?: boolean) => (source: Observable<any>) => {
|
|
|
- if (skipCached) return source.pipe(skip(1),take(1)); // skip first response, which usually is the cached data, take following response from backend
|
|
|
- return source.pipe(take(1)); //takes first response; either cached or live
|
|
|
-}
|
|
|
-
|
|
|
-export class QuotationListingController extends Controller {
|
|
|
-
|
|
|
- protected tag: string = 'qttable';
|
|
|
- protected submitted:boolean;
|
|
|
-
|
|
|
- /**has submit submit / allow for submit */
|
|
|
- protected submitable: boolean = true;
|
|
|
-
|
|
|
- /** latest tender submission document */
|
|
|
- protected submittedItems: {[key:string]: Document};
|
|
|
-
|
|
|
- private _table: BehaviorSubject<UITable> = new BehaviorSubject({
|
|
|
- fields: [],
|
|
|
- data: []
|
|
|
- });
|
|
|
-
|
|
|
- protected options: Table.Options = {
|
|
|
- showAppendControls: false
|
|
|
- }
|
|
|
-
|
|
|
- protected eventListener: Subject<Event> = new Subject();
|
|
|
-
|
|
|
- protected process: Process = new Process();
|
|
|
-
|
|
|
- private activeEntryNo;
|
|
|
- private errors = [];
|
|
|
- private successMessages = [];
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- constructor(
|
|
|
- private store: Store,
|
|
|
- private tms: TMService,
|
|
|
- private cs: ComponentService,
|
|
|
- private fieldColumns: any[],
|
|
|
- public document: Document,
|
|
|
- private documentCache: Document[],
|
|
|
- private untilDestroy?: MonoTypeOperatorFunction<any>
|
|
|
- ) {
|
|
|
- super();
|
|
|
- if (!this.document.editState) this.document.editState = "VIEW";
|
|
|
-
|
|
|
- // get the document header data, check if document had been posted
|
|
|
- this.tms.data.get({
|
|
|
- serviceId: ServiceID.DocumentHeaderData,
|
|
|
- parameter: `id=${this.document.docId}`
|
|
|
- },this.process).pipe(skipCache(true)).subscribe(res => {
|
|
|
- const data = res.data?.GenericFisData?.data?.DataService?.rows?.row[0]?.column;
|
|
|
- this.generateFields(fieldColumns);
|
|
|
- this.update().then((res) => {
|
|
|
- this.table = {
|
|
|
- ...this.table,
|
|
|
- data: res
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- generateFields(fieldColumns) {
|
|
|
- let fields = new Fields([]);
|
|
|
- let tempColumns = {};
|
|
|
- documentSchema.details.visibleFields.forEach(key => {
|
|
|
- if (!fieldColumns) window.location.reload();
|
|
|
- let column = fieldColumns[key];
|
|
|
- tempColumns = {
|
|
|
- ...tempColumns,
|
|
|
- [key]: column
|
|
|
- };
|
|
|
- });
|
|
|
- fields = new Fields(this.tms.fields.convert(tempColumns));
|
|
|
- fields.map((f: Field) => {
|
|
|
- f.scope = f.key;
|
|
|
- if (f.type === 'number') {f.attributes.align='right';f.attributes.decimal='2'};
|
|
|
- if (f.key === 'prd_uom') {f.attributes.align='center';};
|
|
|
- if (f.key === 'prd_qty_credit') {f.attributes.decimal='0';};
|
|
|
- if (f.key === 'entry_ref') {f.label= {key:'prd_code',default:'Item Code'}};
|
|
|
- if (f.key === 'prd_price') {f.attributes.hint = {key:'enter_unit_price',default:'Please enter unit price'}};
|
|
|
- if (f.key === 'prd_price' && this.submitted === true) {f.attributes.readonly = true};
|
|
|
- if (f.type === 'date' || f.type === 'datetime') f.attributes.dateFormat = 'yyyy-MM-dd';
|
|
|
- if (this.document.notEditable()) f.attributes.readonly = true;
|
|
|
- });
|
|
|
- this.table.fields = fields;
|
|
|
- this.table.editColDef = JSON.parse(JSON.stringify(documentSchema.details.editColDef));
|
|
|
- return fields;
|
|
|
- }
|
|
|
-
|
|
|
- override update(skipCached?: boolean): Promise<any> {
|
|
|
- return new Promise<any>((resolve,reject) => {
|
|
|
- // get quoted items listed by tenderee (tenderee offers contract)
|
|
|
- const getInviteItems = this.tms.data.get({
|
|
|
- serviceId: ServiceID.DetailData,
|
|
|
- parameter: '__clearSearchValue__=true,doctypecodelist=S2'
|
|
|
- },this.process,{caching:"false"}).pipe(takeWhile(res => res !== undefined), // skip undefined responses
|
|
|
- skipCache(skipCached),
|
|
|
- map((res:any) => {
|
|
|
- const rows = res?.data?.GenericFisData?.data?.DataService?.rows?.row?.
|
|
|
- filter(x => x.column.doc_id === this.document.docId)?.
|
|
|
- map(x => {return x.column});
|
|
|
- return rows?rows:[];
|
|
|
- }));
|
|
|
-
|
|
|
- // get quoted items submitted by current tenderer (tenderer submits quotation offered by tenderee)
|
|
|
- const getSubmittedItems = this.tms.data.get({
|
|
|
- serviceId: ServiceID.DetailData,
|
|
|
- parameter: '__clearSearchValue__=true,doctypecodelist=S3'
|
|
|
- },null,{caching:"false"}).pipe(takeWhile(res => res !== undefined), // skip undefined responses
|
|
|
- skipCache(skipCached),
|
|
|
- map((res:any) => {
|
|
|
- let rows = res?.data?.GenericFisData?.data?.DataService?.rows?.row?.
|
|
|
- filter(x => x.column.allocatedtodocid === this.document.docId)?.
|
|
|
- map(x => {return {...x.column,rowId:x.rowId,rowNumber:x.rowNumber}});
|
|
|
- if (rows?.length > 0) {
|
|
|
- const latestSubmission = rows.reduce((prev,curr) => {
|
|
|
- const prevDate = new Date(prev.doc_dt);
|
|
|
- const currDate = new Date(curr.doc_dt);
|
|
|
- return (prevDate > currDate)?prev:curr
|
|
|
- });
|
|
|
- this.submittedItems = rows.filter(x => x.doc_id === latestSubmission.doc_id)?.reduce((prev,curr) => {
|
|
|
- prev[curr.allocatedtodocentryno] = prev[curr.allocatedtodocentryno] || [];
|
|
|
- prev[curr.allocatedtodocentryno].push(curr);
|
|
|
- return prev;
|
|
|
- },{});
|
|
|
- }
|
|
|
- return rows?rows:[];
|
|
|
- }));
|
|
|
-
|
|
|
- getInviteItems.pipe(map(async inviteItems => {
|
|
|
- await firstValueFrom(getSubmittedItems).catch(error => console.error(error)).then(submittedItems => {});
|
|
|
- inviteItems = inviteItems.map(item => {
|
|
|
- if (this.submittedItems && this.submittedItems[item.entry_no_a0]) {
|
|
|
- item = {...item,prd_price_original:item.prd_price,prd_price:this.submittedItems[item.entry_no_a0][0].prd_price,
|
|
|
- entry_cur_credit:this.submittedItems[item.entry_no_a0][0].entry_cur_credit
|
|
|
- };
|
|
|
- }
|
|
|
- return item;
|
|
|
- });
|
|
|
- return inviteItems;
|
|
|
- })).subscribe({
|
|
|
- next: promise => {
|
|
|
- promise.catch(error => reject(error)).then(res => {
|
|
|
- res.map(row => {
|
|
|
- const prd_price = parseFloat(row.prd_price)?.toFixed(2);
|
|
|
- if (!row.prd_price_original) row.prd_price_original = 0;
|
|
|
- const prd_price_original = parseFloat(row.prd_price_original)?.toFixed(2);
|
|
|
- row.prd_price_hint = "Original requested price: "+prd_price_original;
|
|
|
- row.prd_uom = row.prd_package_name;
|
|
|
- row.prd_qty_credit = parseInt(row.prd_qty_credit);
|
|
|
- row.prd_price = prd_price;
|
|
|
- row.entry_cur_credit = row.prd_price * row.prd_qty_credit,
|
|
|
- row.checked = false; //set default checkbox state to unchecked;
|
|
|
- row.checkbox_hint = 'check to bid';
|
|
|
- });
|
|
|
- let data: any[] = res;
|
|
|
- resolve(data);
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- protected onChangeListener(event: Event) {
|
|
|
- switch (event.name) {
|
|
|
- case TreeTableEvent.ValueChange().name: {
|
|
|
- this.onValueChangeEvent(event);
|
|
|
- break;
|
|
|
- }
|
|
|
- case TreeTableEvent.InputFocus().name: {
|
|
|
- this.errors = [];
|
|
|
- switch (true) {
|
|
|
- // no existing tender submission document:
|
|
|
- case this.document.editState === "VIEW" && !this.submittedItems : {
|
|
|
- this.sendNewCommand()?.then(newRes => {
|
|
|
- this.sendExecuteCommand(event.payload);
|
|
|
- });
|
|
|
- break;
|
|
|
- }
|
|
|
- // has existing tender submission document:
|
|
|
- case this.document.editState === "VIEW" && this.submittedItems !== undefined : {
|
|
|
- this.sendExecuteCommand(event.payload);
|
|
|
- break;
|
|
|
- }
|
|
|
- // when another row is selected:
|
|
|
- case (this.document.editState === 'NEW' || this.document.editState === 'MODIFY') &&
|
|
|
- event.payload?.entry_no_a0 !== this.activeEntryNo &&
|
|
|
- !this.process.hasActiveProcesses: {
|
|
|
- this.sendExecuteCommand(event.payload);
|
|
|
- break;
|
|
|
- }
|
|
|
- default: break;
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
- default: break;
|
|
|
- }
|
|
|
- //this.eventListener.next(event);
|
|
|
- }
|
|
|
-
|
|
|
- sendNewCommand() {
|
|
|
- const NEW = new Observable(observer => {
|
|
|
- const processId = this.process.addActiveProcess('New');
|
|
|
- this.tms.command({
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- operation: Ops.NEW,
|
|
|
- payload: {
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- }
|
|
|
- }).pipe(take(1)).subscribe({
|
|
|
- next: res => {
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- this.document.editState = "NEW";
|
|
|
- this.document.submitted = false;
|
|
|
- observer.next(res);
|
|
|
- observer.complete();
|
|
|
- },
|
|
|
- error: error => {
|
|
|
- observer.error(error);
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
- const SET_TENDERER_ID = new Observable(observer => {
|
|
|
- const processId = this.process.addActiveProcess('SetColumn - tendererid');
|
|
|
- this.tms.setColumn({
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- alias: 'header',
|
|
|
- column: {
|
|
|
- row: 1,
|
|
|
- name: 'tendererid',
|
|
|
- value: this.document.tendererId
|
|
|
- }
|
|
|
- }).pipe(take(1)).subscribe({
|
|
|
- next: res => {
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- observer.next(res);
|
|
|
- observer.complete();
|
|
|
- },
|
|
|
- error: error => {
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- observer.error(error);
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
-
|
|
|
- return !this.submitted?lastValueFrom(concat(NEW, SET_TENDERER_ID)):null;
|
|
|
- }
|
|
|
-
|
|
|
- /**retrieve tender submission document
|
|
|
- * @param {{doc_id,entry_no_a0}} payload
|
|
|
- * @param {string} [parameter] Execute parameter
|
|
|
- */
|
|
|
- sendExecuteCommand(payload, parameter?: string) {
|
|
|
- if (!parameter) parameter = `eventname=ue_trigger_copy,doc_id=${payload.doc_id},entry_no=${payload.entry_no_a0}`;
|
|
|
- this.submitted = true;
|
|
|
- this.activeEntryNo = payload.entry_no_a0;
|
|
|
- const EXECUTE = new Observable(observer => {
|
|
|
- const processId = this.process.addActiveProcess('Execute');
|
|
|
- this.tms.command({
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- operation: Ops.Execute,
|
|
|
- messageName: 'Retrieve Tender Submission',
|
|
|
- payload:{
|
|
|
- //parameter: `objecttype=TM,eventname=ue_trigger_copy,doc_id=836569,entry_no=2`,
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- parameter: `objecttype=TM,${parameter}`,
|
|
|
- dataRow: null
|
|
|
- }
|
|
|
- }).pipe(take(1)).subscribe({
|
|
|
- next: res => {
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- observer.complete();
|
|
|
- },
|
|
|
- error: error => {
|
|
|
- observer.error(error);
|
|
|
- observer.complete();
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- });
|
|
|
- const GET_DATA_AFTER_EXECUTE = new Observable(observer => {
|
|
|
- const processId = this.process.addActiveProcess('GetData after Execute');
|
|
|
- this.tms.query(
|
|
|
- ServiceID.Submission
|
|
|
- ).pipe(take(1)).subscribe({
|
|
|
- next: res => {
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- observer.complete();
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
-
|
|
|
- concat(EXECUTE,GET_DATA_AFTER_EXECUTE).subscribe();
|
|
|
- }
|
|
|
-
|
|
|
- sendCancelChangesCommand(): Promise<unknown> {
|
|
|
- this.errors = [];
|
|
|
- this.successMessages = [];
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const processId = this.process.addActiveProcess('Cancel Changes');
|
|
|
- this.tms.command({
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- operation: Ops.CANCEL_CHANGES,
|
|
|
- payload: {
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- }
|
|
|
- }).pipe(take(1)).subscribe({
|
|
|
- next: res => {
|
|
|
- this.documentCache.map(doc => {
|
|
|
- if (doc.docId !== this.document.docId) {
|
|
|
- doc.listingController.document.editState = 'VIEW';
|
|
|
- }
|
|
|
- return doc;
|
|
|
- });
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- resolve(res);
|
|
|
- },
|
|
|
- error: error => reject(error)
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- private onValueChangeEvent(event: Event) {
|
|
|
- if (this.document.editState !== 'VIEW') new Observable(observer => {
|
|
|
- const processId = this.process.addActiveProcess(`SetColumn - ${event.payload?.field}`);
|
|
|
- this.tms.setColumn({
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- alias: 'details',
|
|
|
- column: {
|
|
|
- row: event.payload?.entry_no_a0,
|
|
|
- name: event.payload?.field,
|
|
|
- value: event.payload[event.payload?.field]
|
|
|
- }
|
|
|
- }).pipe(take(1)).subscribe({
|
|
|
- next: res => {
|
|
|
- this.setAmount(event.payload).then(amount => {
|
|
|
- observer.complete();
|
|
|
- const data = this.table.data;
|
|
|
- data.map(row => {
|
|
|
- if (row.entry_no_a0 === event.payload?.entry_no_a0) {
|
|
|
- row[event.payload?.field+'_error'] = undefined;
|
|
|
- row.entry_cur_credit = amount;
|
|
|
- }
|
|
|
- });
|
|
|
- this.table = {
|
|
|
- ...this.table,
|
|
|
- data: data
|
|
|
- }
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- });
|
|
|
- },
|
|
|
- error: error => {
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- const data = this.table.data;
|
|
|
- data.map(row => {
|
|
|
- if (row.entry_no_a0 === event.payload?.entry_no_a0) {
|
|
|
- row[event.payload?.field+'_error'] = error.message;
|
|
|
- }
|
|
|
- });
|
|
|
- this.table = {
|
|
|
- ...this.table,
|
|
|
- data: data
|
|
|
- }
|
|
|
- observer.error(error)
|
|
|
- }
|
|
|
- });
|
|
|
- }).subscribe();
|
|
|
- }
|
|
|
-
|
|
|
- onSave() {
|
|
|
- this.errors = [];
|
|
|
- const processId = this.process.addActiveProcess('Save');
|
|
|
- new Promise((resolve, reject) => {
|
|
|
- this.tms.command({
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- operation: Ops.SAVE
|
|
|
- }).pipe(take(1)).subscribe({
|
|
|
- next: save => {
|
|
|
- this.document.editState = 'VIEW';
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- this.successMessages = [this.cs.getLabel('saved','Saved')];
|
|
|
- // this.update(true).then(res => {
|
|
|
- // this.table = {
|
|
|
- // ...this.table,
|
|
|
- // data: res
|
|
|
- // }
|
|
|
- // });
|
|
|
- resolve(save);
|
|
|
-
|
|
|
- },
|
|
|
- error: error => reject(error)
|
|
|
- });
|
|
|
-
|
|
|
- }).catch(error => {
|
|
|
- console.error(error);
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- this.errors.push(error.message);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- onSubmit() {
|
|
|
- this.errors = [];
|
|
|
- this.successMessages = [];
|
|
|
- const POST = new Observable<any>(observer => {
|
|
|
- const processId = this.process.addActiveProcess(`Post (${ServiceID.Submission})`);
|
|
|
- this.tms.command({
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- operation: Ops.POST
|
|
|
- }).pipe(take(1)).subscribe({
|
|
|
- next: res => {
|
|
|
- observer.next(res);
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- this.document.editState = "VIEW";
|
|
|
- this.document.submitted = true;
|
|
|
- let fields: Fields = new Fields(this.table.fields);
|
|
|
- let priceF: Field = fields.find(x => x.key === 'prd_price');
|
|
|
- fields.replace('prd_price',{
|
|
|
- ...priceF,
|
|
|
- attributes:{...priceF.attributes,readonly:true}
|
|
|
- });
|
|
|
- this.table = {
|
|
|
- ...this.table,
|
|
|
- fields: fields
|
|
|
- };
|
|
|
- observer.complete();
|
|
|
- },
|
|
|
- error: error => {
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- observer.error(error);
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
-
|
|
|
- this.cs.dialog.showComfirmDialog({
|
|
|
- title: 'Submit',
|
|
|
- content: 'Do you want to submit this tender?<div><i><small><b>You\'ll no longer be able to edit this tender after submitting.</b></small></i></div>',
|
|
|
- confirm: () => {
|
|
|
- POST.subscribe({
|
|
|
- next: res => {
|
|
|
- this.successMessages = [this.cs.getLabel('submitted','Submitted')];
|
|
|
- },
|
|
|
- error: error => this.errors = [error.message]
|
|
|
- })
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- private setAmount(payload) {
|
|
|
- return new Promise<number>((resolve, reject) => {
|
|
|
- const amount = +payload.prd_price * payload.prd_qty_credit;
|
|
|
- const found = this.table.data.find(x => x.allocatedToDocEntryNo === payload.allocatedToDocEntryNo);
|
|
|
- if (this.document.editState !== 'VIEW') new Observable(observer => {
|
|
|
- const processId = this.process.addActiveProcess(`SetColumn - entry_cur_credit`);
|
|
|
- this.tms.setColumn({
|
|
|
- serviceId: ServiceID.Submission,
|
|
|
- alias: 'details',
|
|
|
- column: {
|
|
|
- row: payload?.entry_no_a0,
|
|
|
- name: 'entry_cur_credit',
|
|
|
- value: amount
|
|
|
- }
|
|
|
- }).pipe(take(1)).subscribe({
|
|
|
- next: res => {
|
|
|
- observer.complete();
|
|
|
- this.process.endActiveProcess(processId);
|
|
|
- },
|
|
|
- error: error => observer.error(error)
|
|
|
- })
|
|
|
- }).subscribe();
|
|
|
- resolve(amount);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- get table() {
|
|
|
- return this._table.getValue();
|
|
|
- }
|
|
|
-
|
|
|
- set table(table) {
|
|
|
- this._table.next(table);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-}
|