1281 lines
39 KiB
TypeScript
1281 lines
39 KiB
TypeScript
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
|
import { ActivatedRoute } from '@angular/router';
|
|
|
|
import { Subject, Observable, timer } from 'rxjs';
|
|
import { switchMap, takeUntil, retryWhen, delayWhen, take } from 'rxjs/operators';
|
|
|
|
import { MenuItem, SelectItem, SelectItemGroup } from 'primeng/api';
|
|
import { FileUpload } from 'primeng/fileupload';
|
|
|
|
import { saveAs } from 'file-saver';
|
|
import cloneDeep from 'clone-deep';
|
|
import { NumUtils, StringUtils, UnitUtils, Utils } from '@app/shared/utils';
|
|
|
|
import { MODE, AppProduct, ITEM, LoadOption, IUIJob } from '../models/job.model';
|
|
import * as fromEntity from '@app//entities/reducers';
|
|
import * as productActions from '@app//entities/actions/product.actions';
|
|
import * as pilotActions from '@app//entities/actions/pilot.actions';
|
|
import * as vehicleActions from '@app//entities/actions/vehicle.actions';
|
|
import * as authActions from '@app//auth/actions/auth.actions';
|
|
import * as jobActions from '../actions/job.actions';
|
|
import * as fromCostingItems from '@app/invoices/reducers';
|
|
|
|
import { JobService } from '@app/domain/services/job.service';
|
|
|
|
import { createNewProduct } from '@app/entities/models/product.model';
|
|
import { createNewPilot } from '@app/entities/models/pilot.model';
|
|
import { createNewVehicle } from '@app/entities/models/vehicle.model';
|
|
|
|
import { RoleIds, globals, ProdType, jobInvoiceStatus, ProdTypes, Units, GC } from '@app/shared/global';
|
|
import { AppComponent } from '@app/app.component';
|
|
import { UnitPipe } from '@app/shared/pipes/unit.pipe';
|
|
import { ProductTypePipe } from '@app/shared/pipes/product-type.pipe';
|
|
import { Crop } from '@app/entities/models/crop.model';
|
|
import { AppFile } from '@app/domain/models/shared.model';
|
|
import { BaseComp } from '@app/shared/base/base.component';
|
|
import { InvoiceService } from '@app/domain/services/invoice.service';
|
|
import { CostingItemUnitPipe } from '@app/invoices/pipes/costing-item-unit.pipe';
|
|
import { Location } from '@angular/common';
|
|
import { InputNumber } from 'primeng/inputnumber';
|
|
import { selectLimit } from '@app/reducers';
|
|
import { Acre } from '@app/domain/models/subscription.model';
|
|
import { SUB, SubTexts, SubType } from '@app/profile/common';
|
|
|
|
|
|
@Component({
|
|
selector: 'agm-job-edit',
|
|
templateUrl: './job-edit.component.html',
|
|
styleUrls: ['./job-edit.component.css']
|
|
})
|
|
export class JobEditComponent extends BaseComp implements OnInit, AfterViewInit, OnDestroy {
|
|
readonly globals = globals;
|
|
readonly GC = GC;
|
|
readonly ITEM = ITEM;
|
|
readonly UnitUtils = UnitUtils;
|
|
readonly MAX_VALUE = 999999;
|
|
readonly MIN_VALUE = 0;
|
|
readonly SubTexts = SubTexts;
|
|
readonly resolveFieldData = Utils.resolveFieldData;
|
|
readonly NAME = 'name';
|
|
readonly TYPE = 'type';
|
|
readonly RATE = 'rate';
|
|
readonly ACTION = 'action';
|
|
readonly TOOLS = 'tools';
|
|
readonly SIZE = 'size';
|
|
readonly WHEN = 'when';
|
|
|
|
private mode = MODE.NONE;
|
|
private stoppedImportPoll;
|
|
private okDl = false;
|
|
|
|
prodCols: any[];
|
|
uploadCols: any[];
|
|
|
|
curProduct: any;
|
|
prodRate = 10; // default rate/area unit value
|
|
prodUnit;
|
|
selectedItem: IUIJob;
|
|
pollingImport = false;
|
|
importStatus = 0;
|
|
useDefRate = false;
|
|
|
|
@ViewChild('newPilot') newPilot: ElementRef;
|
|
addingNewPilot = false;
|
|
|
|
@ViewChild('newProduct') newProduct: ElementRef;
|
|
addingNewProduct = false;
|
|
|
|
@ViewChild('jobNameRef') jobName: ElementRef;
|
|
|
|
@ViewChild('newVehicle') newVehicle: ElementRef;
|
|
addingNewVehicle = false;
|
|
|
|
@ViewChild('uploader') uploader: FileUpload;
|
|
@ViewChild('billableArea') billableArea: InputNumber;
|
|
@ViewChild('quantity') quantity: InputNumber;
|
|
|
|
rateUnits: SelectItem[];
|
|
prodUnits: SelectItem[];
|
|
updateOps: SelectItem[];
|
|
uploadSettings: any;
|
|
status: SelectItem[] = [...GC.selJobStatuses];
|
|
prodTypes: SelectItem[];
|
|
byAreaUnit: string;
|
|
byAreaUnitL: string;
|
|
|
|
grpedProds: SelectItemGroup[] = [];
|
|
|
|
srcUsers: any[];
|
|
tarUsers: any[];
|
|
|
|
uploadUrl = '/imports/uploadJob';
|
|
uploadedFiles = [];
|
|
dlLogs = [];
|
|
uploadErrorMsg = '';
|
|
|
|
cloneId = 0;
|
|
curAppId; // Current Application Id of the current importing file
|
|
|
|
canWrite = false;
|
|
|
|
dlOps: SelectItem[];
|
|
|
|
pilots: SelectItem[] = [];
|
|
vehicles: SelectItem[] = [];
|
|
downloadOptions: MenuItem[];
|
|
crops: Crop[];
|
|
addingNewCropJob = false;
|
|
|
|
totalCoverage: number;
|
|
|
|
private _job: IUIJob = null;
|
|
get job(): IUIJob { return this._job; }
|
|
set job(job: IUIJob) {
|
|
job.loadOp.date = new Date(job.loadOp.date);
|
|
|
|
this._job = job;
|
|
this.selectedItem = cloneDeep(job, true);
|
|
this.checkOKDl();
|
|
}
|
|
|
|
get canDownload() {
|
|
return this.okDl;
|
|
}
|
|
|
|
get defaultRateUnit(): number {
|
|
return UnitUtils.defRateUnit(this.selectedItem.measureUnit);
|
|
}
|
|
|
|
get isArchived(): boolean {
|
|
return this.selectedItem.isArchived;
|
|
}
|
|
|
|
loadTypes: SelectItem[] = [
|
|
{ label: $localize`:@@normal:Normal`, value: 0 },
|
|
{ label: $localize`:@@equal:Equal`, value: 1 }
|
|
];
|
|
loadSheetDlgOn: boolean = false;
|
|
|
|
acre: Acre;
|
|
|
|
// invoice costing section
|
|
costingItems: SelectItem[]; // Costing items dropdown list
|
|
costingItemForm;
|
|
currencyUnit;
|
|
costingErrorMsg = '';
|
|
|
|
get canAccessInvoice() {
|
|
return this.authSvc.canAccessInvoice;
|
|
}
|
|
|
|
get canCreateInvoice() {
|
|
return this.job
|
|
&& this.job.status != 0
|
|
&& this.job.costings?.billableAmount
|
|
&& this.job.invoiceStatus == jobInvoiceStatus.NONE
|
|
&& !this.isBillableAreaRequired
|
|
&& this.canAccessInvoice;
|
|
}
|
|
|
|
get canViewInvoice() {
|
|
return this.selectedItem
|
|
&& this.selectedItem.invoiceId
|
|
&& this.selectedItem.invoiceStatus == jobInvoiceStatus.INVOICED;
|
|
}
|
|
|
|
get canEditCosting() {
|
|
return this.selectedItem && this.selectedItem.invoiceStatus !== jobInvoiceStatus.INVOICED;
|
|
}
|
|
|
|
get costingChanged() {
|
|
return !Utils.deepEquals(this.job.costings, this.selectedItem.costings);
|
|
}
|
|
|
|
get isBillableAreaRequired(): boolean {
|
|
return !this.selectedItem.costings.billableArea && this.selectedItem.costings.items.length > 0 && this.isBillableStatus(this.selectedItem.status);
|
|
}
|
|
|
|
constructor(
|
|
public app: AppComponent,
|
|
private readonly jobSvc: JobService,
|
|
private readonly route: ActivatedRoute,
|
|
private readonly unitPipe: UnitPipe,
|
|
private readonly costingItemUnitPipe: CostingItemUnitPipe,
|
|
private readonly prodTypePipe: ProductTypePipe,
|
|
protected readonly cdRef: ChangeDetectorRef,
|
|
private readonly ngZone: NgZone,
|
|
private readonly invoiceSvc: InvoiceService,
|
|
private readonly location: Location
|
|
) {
|
|
super(cdRef);
|
|
this.init();
|
|
this.prodCols = [
|
|
{ field: this.NAME, header: $localize`:@@name:Name`, width: "*" },
|
|
{ field: this.TYPE, header: $localize`:@@type:Type`, width: "14%" },
|
|
{ field: this.RATE, header: $localize`:@@rate:Rate`, width: "14%" },
|
|
{ field: 'unit', header: $localize`:@@unit:Unit`, width: "11%" },
|
|
{ field: this.ACTION, header: $localize`:@@action:Action`, width: "11%" },
|
|
];
|
|
this.uploadCols = [
|
|
{ field: this.NAME, header: $localize`:@@name:Name`, width: "48%" },
|
|
{ field: this.SIZE, header: $localize`:@@size:Size`, width: "13%" },
|
|
{ field: this.WHEN, header: $localize`:@@when:When` },
|
|
{ field: this.TOOLS, header: $localize`:@@tools:Tools`, width: "11%" },
|
|
];
|
|
this.costingItemForm = {
|
|
selCostingItem: null,
|
|
quantity: null
|
|
};
|
|
}
|
|
|
|
private init() {
|
|
this.stoppedImportPoll = new Subject();
|
|
this.downloadOptions = [
|
|
{
|
|
label: globals.agnav, icon: '', command: () => {
|
|
this.downLoadJob(1);
|
|
}
|
|
},
|
|
{
|
|
label: globals.agnavPrj, icon: '', command: () => {
|
|
this.downLoadJob(2);
|
|
}
|
|
},
|
|
{
|
|
label: globals.esriShape, icon: '', command: () => {
|
|
this.downLoadJob(3);
|
|
}
|
|
},
|
|
];
|
|
this.updateOps = [
|
|
{ label: $localize`:Overwrite all jobs items with what found in the file@@overwrite:Overwrite`, value: '3' },
|
|
{ label: $localize`:Import data from the zip file only@@dataOnly:Data Only`, value: '1' },
|
|
{ label: $localize`:Exclusion zones@@xcls:XCLs`, value: '4' },
|
|
];
|
|
this.status = [
|
|
{ label: globals.statusNew, value: 0 },
|
|
{ label: globals.statusReady, value: 1 },
|
|
{ label: globals.statusDownloaded, value: 2 },
|
|
{ label: globals.statusSprayed, value: 3 },
|
|
{ label: globals.statusArchived, value: 9 },
|
|
];
|
|
|
|
this.dlOps = [
|
|
{ label: globals.agnav, value: 1 },
|
|
{ label: globals.agnavPrj, value: 2 },
|
|
{ label: globals.esriShape, value: 3 }
|
|
];
|
|
|
|
this.uploadSettings = !Utils.isEmptyObj(this.appConf.settings.upload) ? Object.assign({}, this.appConf.settings.upload) : { updateOp: '2' };
|
|
this.canWrite = this.authSvc.hasRole([RoleIds.APP, RoleIds.APP_ADM, RoleIds.OFFICER, RoleIds.PILOT, RoleIds.CLIENT]);
|
|
|
|
this.prodTypes = [
|
|
{ label: ProdTypes[ProdType.ACTIVE], value: ProdType.ACTIVE },
|
|
{ label: ProdTypes[ProdType.CARRIER], value: ProdType.CARRIER },
|
|
];
|
|
this.prodUnits = [
|
|
{ label: this.unitPipe.transform(Units.OZ), value: Units.OZ },
|
|
{ label: this.unitPipe.transform(Units.GAL), value: Units.GAL },
|
|
{ label: this.unitPipe.transform(Units.LIT), value: Units.LIT },
|
|
{ label: this.unitPipe.transform(Units.LB), value: Units.LB },
|
|
{ label: this.unitPipe.transform(Units.KG), value: Units.KG }
|
|
];
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.sub$ = this.route.data
|
|
.subscribe((data) => {
|
|
const job = data[0].job as IUIJob || null;
|
|
this.mode = data[0].mode;
|
|
if (!job) {
|
|
return;
|
|
}
|
|
|
|
if (this.mode === MODE.CLONE) {
|
|
delete job.invoiceId;
|
|
delete job.invoiceStatus;
|
|
}
|
|
|
|
if (!job.costings.currency) {
|
|
this.currencyUnit = this.invoiceSvc.defaultSetting.currency;
|
|
job.costings.currency = this.invoiceSvc.defaultSetting.currency;
|
|
} else {
|
|
this.currencyUnit = job.costings.currency;
|
|
}
|
|
|
|
if (!job.costings.billableArea) {
|
|
job.costings.billableArea = job.ttSprArea ?? 0;
|
|
}
|
|
|
|
const defUnitChanged = this.mode != MODE.EDIT && ['pt', 'es'].indexOf(this.authSvc.locale) != -1;
|
|
if (defUnitChanged) {
|
|
job.measureUnit = false;
|
|
}
|
|
this.updateUnits(job.measureUnit);
|
|
this.job = job;
|
|
|
|
this.validateUnits();
|
|
|
|
if (this.mode === MODE.CLONE) {
|
|
this.cloneId = job._id;
|
|
this.resetJob();
|
|
} else if (this.mode === MODE.EDIT) {
|
|
this.checkImportingStatus();
|
|
}
|
|
});
|
|
|
|
// // Populate dropdown items and subcribe to auto refresh the list if changed
|
|
this.sub$.add(this.store.select(fromEntity.getAllProducts).subscribe(products => {
|
|
this.grpedProds = [];
|
|
if (products) {
|
|
const prods = products.slice(0).map(it => {
|
|
if (!it['type']) {
|
|
it['type'] = 1;
|
|
}
|
|
return it;
|
|
});
|
|
if (!Utils.isEmptyArray(prods)) {
|
|
const grItems = Utils.groupBy(prods, p => p.type);
|
|
for (const key in grItems) {
|
|
this.grpedProds.push({ label: this.prodTypePipe.transform(key), items: grItems[key].map(i => ({ label: i['name'], value: i })) });
|
|
}
|
|
}
|
|
if (this.grpedProds[0]?.items && this.grpedProds[0].items.length) {
|
|
this.onProductChanged(this.grpedProds[0].items[0].value);
|
|
}
|
|
}
|
|
}));
|
|
this.sub$.add(this.store.select(fromEntity.getAllPilots).subscribe(pilots => {
|
|
this.pilots = [];
|
|
if (pilots) {
|
|
pilots.forEach(item => {
|
|
this.pilots.push(<SelectItem>{ label: item.name, value: item });
|
|
});
|
|
}
|
|
}));
|
|
this.sub$.add(this.store.select(fromEntity.getAllVehicles).subscribe(vehicles => {
|
|
this.vehicles = [];
|
|
if (!Utils.isEmptyArray(vehicles)) {
|
|
vehicles.forEach(item => {
|
|
this.vehicles.push(<SelectItem>{ label: item.name, value: item });
|
|
});
|
|
}
|
|
}));
|
|
this.sub$.add(this.store.select(fromEntity.getAllCrops).subscribe(crops => {
|
|
this.crops = [];
|
|
if (crops) {
|
|
this.crops = crops;
|
|
}
|
|
}));
|
|
|
|
this.sub$.add(this.store.select(fromCostingItems.getAllCostingItems).subscribe(costingItems => {
|
|
this.costingItems = (costingItems || [])
|
|
.filter(item => item)
|
|
.map(item => ({
|
|
label: `${item.name} - ${this.costingItemUnitPipe.transform(item.unit)} - ${this.invoiceSvc.defaultSetting.currency} ${item.price}`,
|
|
value: item,
|
|
disabled: false
|
|
}));
|
|
|
|
this.disableExistingCostingItems();
|
|
}));
|
|
|
|
this.sub$.add(this.appActions.ofType(productActions.CREATE_SUCCESS)
|
|
.subscribe((action) => {
|
|
this.curProduct = action['payload'];
|
|
this.onProductChanged(this.curProduct);
|
|
this.addingNewProduct = false;
|
|
}));
|
|
this.sub$.add(this.appActions.ofType(pilotActions.CREATE_SUCCESS).subscribe((action) => {
|
|
this.cancelNewPilot();
|
|
this.selectedItem.operator = action['payload'];
|
|
}));
|
|
this.sub$.add(this.appActions.ofType(vehicleActions.CREATE_SUCCESS).subscribe((action) => {
|
|
this.cancelNewVehicle();
|
|
this.selectedItem.vehicle = action['payload'];
|
|
}));
|
|
|
|
this.sub$.add(this.appActions.ofType(jobActions.CREATE_SUCCESS).subscribe((action) => {
|
|
const job = action['payload'];
|
|
this.editJobMap(job._id);
|
|
}));
|
|
this.sub$.add(this.appActions.ofType(jobActions.UPDATE_SUCCESS).subscribe((action) => {
|
|
const job = action['payload'];
|
|
this.job = { ...this.job, ...job };
|
|
this.resetNewEntities();
|
|
this.checkOKDl();
|
|
}));
|
|
this.sub$.add(this.appActions.ofType(jobActions.ASSIGN_SUCCESS).subscribe((action) => {
|
|
this._job['dlOp'] = this.selectedItem.dlOp;
|
|
}));
|
|
|
|
this.sub$.add(this.store.select(selectLimit(SubType.PACKAGE)).pipe(take(1)).subscribe((pkg) => {
|
|
this.acre = pkg[this.authSvc.getCurLookupKey(SubType.PACKAGE)]?.acre;
|
|
if (this.acre?.overLimit) {
|
|
return this.updateOps.unshift({ label: $localize`:Append to existing jobs items with what found in the file@@append:Append`, value: '2', disabled: true });
|
|
}
|
|
this.updateOps.unshift({ label: $localize`:Append to existing jobs items with what found in the file@@append:Append`, value: '2' });
|
|
}));
|
|
}
|
|
|
|
ngAfterViewInit(): void {
|
|
const timer = setInterval(() => {
|
|
if (this.selectedItem && StringUtils.isEmpty(this.selectedItem.name)) {
|
|
if (this.jobName.nativeElement) {
|
|
this.jobName.nativeElement.focus();
|
|
clearInterval(timer);
|
|
}
|
|
} else {
|
|
clearInterval(timer);
|
|
}
|
|
}, 500);
|
|
setTimeout(() => {
|
|
clearInterval(timer);
|
|
}, 1500);
|
|
|
|
setTimeout(() => {
|
|
if (!this.curProduct && (!Utils.isEmptyArray(this.grpedProds) && !Utils.isEmptyArray(this.grpedProds[0].items))) {
|
|
this.curProduct = this.grpedProds[0].items[0].value;
|
|
}
|
|
|
|
if (this.isEdit) {
|
|
this.getUploadedFiles();
|
|
this.getLogs();
|
|
if (this.isPlanner) {
|
|
this.getAssignments();
|
|
}
|
|
}
|
|
}, 500);
|
|
|
|
if (this.prodUnit === undefined) {
|
|
this.prodUnit = this.job.measureUnit ? Units.GAL : Units.LIT;
|
|
}
|
|
}
|
|
|
|
private validateUnits() {
|
|
if (!this.selectedItem) {
|
|
return;
|
|
}
|
|
|
|
const ok = ((this.selectedItem.measureUnit === true && [0, 1, 2].indexOf(this.selectedItem.appRateUnit) != -1)
|
|
|| (this.selectedItem.measureUnit === false && [3, 4].indexOf(this.selectedItem.appRateUnit) != -1));
|
|
if (!ok) {
|
|
this.selectedItem.appRateUnit = this.defaultRateUnit;
|
|
}
|
|
}
|
|
|
|
private checkOKDl() {
|
|
this.okDl = (this.selectedItem && this.selectedItem.status >= 1) && this.selectedItem.hasItems;
|
|
}
|
|
|
|
private getUploadedFiles() {
|
|
this.jobSvc.getUploadedFiles({ 'jobId': this.job._id }).subscribe((res) => {
|
|
this.uploadedFiles.length = 0;
|
|
if (!(res instanceof Array) || res.length === 0) {
|
|
return;
|
|
}
|
|
for (const item of res) {
|
|
this.addAppFile(item);
|
|
}
|
|
this.updateTotalCoverage();
|
|
});
|
|
}
|
|
|
|
private getLogs(type: number = 2) {
|
|
this.jobSvc.getJobLogs({ 'jobId': this.job._id }).subscribe((res) => {
|
|
this.dlLogs = res;
|
|
});
|
|
}
|
|
|
|
private getAssignments() {
|
|
this.jobSvc.getAssignments({ 'jobId': this.job._id }).subscribe((res) => {
|
|
if (res) {
|
|
this.srcUsers = !Utils.isEmptyArray(res.avUsers) ? res.avUsers.filter(u => u.pkgActive) : [];
|
|
this.tarUsers = res.asUsers;
|
|
}
|
|
});
|
|
}
|
|
|
|
private getAppRateUnits(isUS: boolean) {
|
|
if (isUS) {
|
|
this.rateUnits = [
|
|
{ label: globals.ozPerAc, value: 0 },
|
|
{ label: globals.galPerAc, value: 1 },
|
|
{ label: globals.lbPerAc, value: 2 },
|
|
];
|
|
} else {
|
|
this.rateUnits = [
|
|
{ label: globals.litPerHa, value: 3 },
|
|
{ label: globals.kgPerHa, value: 4 }
|
|
];
|
|
}
|
|
}
|
|
|
|
get isEdit(): boolean {
|
|
return (this.selectedItem && this.selectedItem._id > 0);
|
|
}
|
|
|
|
get canEdit(): boolean {
|
|
return this.authSvc.hasRole([RoleIds.APP, RoleIds.APP_ADM]);
|
|
}
|
|
|
|
isValid(): boolean {
|
|
let valid = true;
|
|
|
|
if (this.addingNewProduct) {
|
|
valid = false;
|
|
}
|
|
|
|
if (this.addingNewPilot && this.newPilot.nativeElement) {
|
|
if (StringUtils.isEmpty(this.newPilot.nativeElement.value)) {
|
|
if (valid) {
|
|
this.newPilot.nativeElement.focus();
|
|
}
|
|
valid = false;
|
|
}
|
|
}
|
|
if (this.addingNewVehicle && this.newVehicle.nativeElement) {
|
|
if (StringUtils.isEmpty(this.newVehicle.nativeElement.value)) {
|
|
if (valid) {
|
|
this.newVehicle.nativeElement.focus();
|
|
}
|
|
valid = false;
|
|
}
|
|
}
|
|
if ((this.selectedItem.startDate && this.selectedItem.endDate)
|
|
&& this.selectedItem.endDate < this.selectedItem.startDate) {
|
|
valid = false;
|
|
this.selectedItem.endDate = this.selectedItem.startDate;
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
onMoveToActiveList(items) {
|
|
if (items && items.length) {
|
|
const inactiveACList = items.filter(i => i.active === false);
|
|
if (inactiveACList.length) {
|
|
this.tarUsers = [...this.tarUsers, ...inactiveACList];
|
|
this.srcUsers = this.srcUsers.filter(u => u.active === true);
|
|
let errMsg = $localize`:@@cannotUnAssignInactiveVehicles:Cannot unassign inactive Aircraft`;
|
|
errMsg += ':[ ' + (inactiveACList.map(u => u.name)).join(',') + ' ]';
|
|
this.msgSvc.addFailedMsg(errMsg);
|
|
}
|
|
}
|
|
}
|
|
|
|
getUserToolTip(user) {
|
|
if (Utils.isEmptyObj(user)) return '';
|
|
let userTT = user.username;
|
|
!user.active && (userTT += `, ${globals.inactive}`);
|
|
return userTT;
|
|
}
|
|
|
|
setNewRefEnties() {
|
|
if (this.addingNewPilot) {
|
|
const newPilot = { _id: 'TMP', name: this.newPilot.nativeElement.value.trim() };
|
|
this.pilots.push({ label: newPilot.name, value: newPilot });
|
|
this.selectedItem.operator = newPilot;
|
|
}
|
|
|
|
if (this.addingNewProduct) {
|
|
const newProduct = { _id: 'TMP', name: this.newProduct.nativeElement.value.trim() };
|
|
this.curProduct = newProduct;
|
|
}
|
|
}
|
|
|
|
createJob() {
|
|
if (!this.selectedItem || !this.isValid()) {
|
|
return;
|
|
}
|
|
|
|
this.setNewRefEnties();
|
|
|
|
const job = this.toJobWithCostings(this.selectedItem);
|
|
|
|
if (this.mode === MODE.CLONE) {
|
|
job['cloneId'] = this.cloneId;
|
|
}
|
|
this.store.dispatch(new jobActions.Create(job));
|
|
}
|
|
|
|
saveJob() {
|
|
if (!this.selectedItem || !this.isValid() || (Utils.deepEquals(this._job, this.selectedItem) && !this.useDefRate)) {
|
|
return;
|
|
}
|
|
|
|
if (this.job.invoiceStatus == jobInvoiceStatus.INVOICED && this.job.costings?.currency != this.selectedItem.costings.currency) {
|
|
this.msgSvc.addFailedMsg($localize`:@@jobInvoiceCurrencyInvalid:This job is already invoiced in #currency# currency! Please void the invoice first or create new jobs.`.replace('#currency#', this.job.costings.currency));
|
|
return;
|
|
}
|
|
|
|
const job = this.toJobWithCostings(this.selectedItem);
|
|
|
|
this.setNewRefEnties();
|
|
this.store.dispatch(new jobActions.Update(<jobActions.UpdateJobOps>{ job, updateItems: false, useDefRate: this.useDefRate }));
|
|
this.resetNewEntities();
|
|
}
|
|
|
|
private toJobWithCostings(job) {
|
|
const hasCostingItems = job?.costings?.items?.length > 0
|
|
if (!hasCostingItems) {
|
|
delete job.costings?.currency;
|
|
}
|
|
return job;
|
|
}
|
|
|
|
addNewProduct() {
|
|
this.addingNewProduct = true;
|
|
}
|
|
|
|
onNewProduct(product) {
|
|
if (!product) {
|
|
this.addingNewProduct = false;
|
|
return;
|
|
}
|
|
const newProd = createNewProduct(this.authSvc.byPUserId);
|
|
Object.assign(newProd, product);
|
|
|
|
this.store.dispatch(new productActions.Create(newProd));
|
|
this.addingNewProduct = false;
|
|
}
|
|
|
|
addNewPilot() {
|
|
this.addingNewPilot = true;
|
|
setTimeout(() => {
|
|
if (this.newPilot) {
|
|
this.newPilot.nativeElement.focus();
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
cancelNewPilot() {
|
|
this.addingNewPilot = false;
|
|
}
|
|
|
|
saveNewPilot() {
|
|
const pilotName = this.newPilot.nativeElement.value;
|
|
if (StringUtils.isEmpty(pilotName)) {
|
|
return;
|
|
}
|
|
const _pilot = createNewPilot(this.authSvc.byPUserId);
|
|
_pilot.name = pilotName;
|
|
this.store.dispatch(new pilotActions.Create(_pilot));
|
|
}
|
|
|
|
addNewVehicle() {
|
|
this.addingNewVehicle = true;
|
|
setTimeout(() => {
|
|
if (this.newVehicle) {
|
|
this.newVehicle.nativeElement.focus();
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
cancelNewVehicle() {
|
|
this.addingNewVehicle = false;
|
|
}
|
|
|
|
saveNewVehicle() {
|
|
const vehicleName = this.newVehicle.nativeElement.value;
|
|
if (StringUtils.isEmpty(vehicleName)) {
|
|
return;
|
|
}
|
|
const _vehicle = createNewVehicle(this.authSvc.byPUserId);
|
|
_vehicle.name = vehicleName;
|
|
this.store.dispatch(new vehicleActions.Create(_vehicle));
|
|
}
|
|
|
|
onStatusChanged(event) {
|
|
if (this.isEndStatus(event.value)) {
|
|
if (!this.selectedItem.endDate) {
|
|
this.selectedItem.endDate = new Date();
|
|
}
|
|
} else if (this.isBillableStatus(event.value)) {
|
|
this.selectedItem.costings.billableArea ?? this.selectedItem.ttSprArea;
|
|
} else {
|
|
this.selectedItem.costings.billableArea = 0;
|
|
}
|
|
}
|
|
|
|
private isEndStatus(status: number): boolean {
|
|
return status === this.status.length - 1;
|
|
}
|
|
|
|
private isBillableStatus(status: number): boolean {
|
|
return [1, 2, 3].includes(status) && this.selectedItem.status > 0;
|
|
}
|
|
|
|
onUnitChanged(event) {
|
|
if (event) {
|
|
this.updateUnits(this.selectedItem.measureUnit);
|
|
this.selectedItem.appRateUnit = this.defaultRateUnit;
|
|
this.onProductChanged(this.curProduct);
|
|
}
|
|
}
|
|
|
|
onProductChanged(prod) {
|
|
if (!prod || !prod.rate) {
|
|
return;
|
|
}
|
|
// Check the value and unit to determine the conversion before update the Job's AppRate with the new value
|
|
const newRate = UnitUtils.toRateUnit(prod.rate.value, prod.rate.unit, this.selectedItem.measureUnit);
|
|
if (newRate.unit) {
|
|
this.prodRate = Number(newRate.value.roundToDecimalPlace(2));
|
|
this.prodUnit = newRate.unit;
|
|
}
|
|
}
|
|
|
|
private updateUnits(measureUnit) {
|
|
this.getAppRateUnits(measureUnit);
|
|
this.prodUnit = measureUnit ? Units.GAL : Units.LIT;
|
|
this.byAreaUnitL = measureUnit ? globals.acre : globals.hectare;
|
|
this.byAreaUnit = measureUnit ? globals.ac : globals.ha;
|
|
}
|
|
|
|
onUpdateOpChanged(event) {
|
|
if (!Utils.equals(this.uploadSettings, this.appConf.settings.upload)) {
|
|
this.appConf.settings.upload = Object.assign({}, this.uploadSettings);
|
|
this.appConf.save({ upload: this.appConf.settings.upload }, true); // Save settings to BE
|
|
}
|
|
}
|
|
|
|
downLoadJob(type: number) {
|
|
this.doDownLoadJob(type);
|
|
}
|
|
|
|
private doDownLoadJob(type) {
|
|
// TODO: Need to be handled in effects ???
|
|
this.jobSvc.downloadJob({ jobId: this.selectedItem._id, type: type }).subscribe(
|
|
(res) => {
|
|
try {
|
|
saveAs(res, `${this.selectedItem.name}_${this.selectedItem._id}.zip`);
|
|
} catch (error) {
|
|
alert('Sorry. Your browser does not support this feature !');
|
|
}
|
|
this.getLogs();
|
|
});
|
|
}
|
|
|
|
editJobMap(id?: number) {
|
|
this.router.navigate(
|
|
[
|
|
`./jobs/${id ? id : this.job._id}/editMap`,
|
|
{ flag: (this.mode === MODE.NEW || this.mode === MODE.CLONE) ? 1 : 2 }
|
|
]);
|
|
}
|
|
|
|
onSelectUpload(event) {
|
|
this.uploadErrorMsg = '';
|
|
if (this.uploader.hasFiles()) {
|
|
this.uploader.upload();
|
|
}
|
|
}
|
|
|
|
onBeforeUpload(event) {
|
|
this.curAppId = '';
|
|
if (event && event.formData) {
|
|
event.formData.append('jobId', this.job._id);
|
|
event.formData.append('updateOp', this.uploadSettings.updateOp);
|
|
}
|
|
}
|
|
|
|
onUpload(event) {
|
|
const resp = event && event.originalEvent ? event.originalEvent : null;
|
|
if (resp) {
|
|
this.uploadErrorMsg = '';
|
|
const res = resp.body;
|
|
if (res && res['_id']) {
|
|
this.curAppId = res['_id'];
|
|
this.checkImportStatus(this.curAppId);
|
|
this.gaSvc.gaEvent('JOBS', 'DATA', 'U');
|
|
}
|
|
}
|
|
}
|
|
|
|
onUploadError(event) {
|
|
if (event && event.error) {
|
|
const resp = event.error;
|
|
const status = resp.status;
|
|
if (status === 401) {
|
|
this.store.dispatch(new authActions.Logout);
|
|
} else if (status > 400) {
|
|
this.importStatus = -1;
|
|
this.uploadErrorMsg = this.uploader.files[0].name + ': ' + globals.apiErrorMsg(resp.error['error']['.tag'] || '');
|
|
}
|
|
this.uploader.clear();
|
|
}
|
|
}
|
|
|
|
private addAppFile(item: AppFile) {
|
|
const newItem = {
|
|
id: item._id,
|
|
name: item.fileName,
|
|
savedName: item.savedFilename,
|
|
size: Utils.formatBytes(item.fileSize, 2),
|
|
when: item.createdDate,
|
|
totalSprayed: item.totalSprayed,
|
|
markedDelete: item.markedDelete
|
|
};
|
|
if (!this.uploadedFiles || this.uploadedFiles.length === 0) {
|
|
this.uploadedFiles = [newItem];
|
|
} else if (this.uploadedFiles.indexOf(newItem) === -1) {
|
|
this.uploadedFiles = this.uploadedFiles.concat(newItem);
|
|
}
|
|
}
|
|
|
|
private checkImportStatus(appId) {
|
|
this.importStatus = 0;
|
|
this.pollingImport = true;
|
|
this.cdRef.detectChanges(); // Workaround to force Angular detects change in *ngIf vars template
|
|
|
|
this.sub$.add(this.pollImportStatus(appId).subscribe(
|
|
data => {
|
|
this.curAppId = data ? data['_id'] : '';
|
|
if (!data) {
|
|
this.stoppedImportPoll.next(true);
|
|
this.pollingImport = false;
|
|
return;
|
|
}
|
|
this.importStatus = data.status;
|
|
if (data.status === 0 || data.status === 3) {
|
|
this.stoppedImportPoll.next(true);
|
|
this.pollingImport = false;
|
|
|
|
if (data.status === 3) {
|
|
if (data.warnMsg) {
|
|
this.showDupResult(data.warnMsg['dup']);
|
|
}
|
|
this.getUploadedFiles();
|
|
} else {
|
|
this.importStatus = -1;
|
|
if (data.errorMsg) {
|
|
this.uploadErrorMsg = data.fileName + ': ' + globals.apiErrorMsg(data.errorMsg);
|
|
}
|
|
this.uploader.clear();
|
|
}
|
|
if (data['proStatus'] >= 10) {
|
|
// TODO: new Action and update current Job when success ???
|
|
this.jobSvc.getJob(this.job._id, false).subscribe(
|
|
data => {
|
|
if (data && this.job !== data) {
|
|
this.job = data;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
},
|
|
error => {
|
|
this.importStatus = 0;
|
|
this.pollingImport = false;
|
|
}));
|
|
}
|
|
|
|
private checkImportingStatus() {
|
|
this.ngZone.run(() => {
|
|
this.importStatus = 0;
|
|
this.pollingImport = true;
|
|
});
|
|
const ob$ = this.pollImportingStatus().subscribe(
|
|
data => {
|
|
this.curAppId = data && data.length ? data[0]['_id'] : '';
|
|
if (!data || !data.length) {
|
|
this.stoppedImportPoll.next(true);
|
|
this.pollingImport = false;
|
|
this.getUploadedFiles();
|
|
}
|
|
},
|
|
error => {
|
|
// TODO: Handle error here possible after couple of attempts not just once??
|
|
this.stoppedImportPoll.next(true);
|
|
this.importStatus = 0;
|
|
this.pollingImport = false;
|
|
});
|
|
if (this.sub$) {
|
|
this.sub$.add(ob$);
|
|
} else {
|
|
this.sub$ = ob$;
|
|
}
|
|
}
|
|
|
|
private pollImportStatus(appId, checkMs: number = 3000): Observable<AppFile> {
|
|
return timer(0, checkMs).pipe(
|
|
switchMap(() => this.jobSvc.getImportStatus(appId)),
|
|
takeUntil(this.stoppedImportPoll),
|
|
retryWhen(errors =>
|
|
errors.pipe(
|
|
delayWhen(val => timer(15 * 1000)) // if error, retry in 15 seconds
|
|
))
|
|
);
|
|
}
|
|
|
|
private pollImportingStatus(checkMs: number = 3000): Observable<AppFile[]> {
|
|
return timer(500, checkMs).pipe(
|
|
switchMap(() => this.jobSvc.getImportingStatus({ jobId: this.job._id })),
|
|
takeUntil(this.stoppedImportPoll));
|
|
}
|
|
|
|
cancelProcessFile() {
|
|
if (!this.curAppId) {
|
|
return;
|
|
}
|
|
this.jobSvc.cancelImporting([this.curAppId]).subscribe((data) => {
|
|
this.ngZone.run(() => {
|
|
this.stoppedImportPoll.next(true);
|
|
this.pollingImport = false;
|
|
});
|
|
this.getUploadedFiles();
|
|
},
|
|
error => {
|
|
this.msgSvc.addFailedMsg($localize`:@@cancelFileFailed#Failed to cancel processing uploaded file:Cannot cancel file`);
|
|
});
|
|
}
|
|
|
|
private updateTotalCoverage() {
|
|
let totalCoverage = 0;
|
|
for (const fi of this.uploadedFiles) {
|
|
if (fi.totalSprayed) {
|
|
totalCoverage += fi.totalSprayed;
|
|
}
|
|
}
|
|
this.totalCoverage = totalCoverage;
|
|
}
|
|
|
|
deleteFile(appFile) {
|
|
this.confirmSvc.confirm({
|
|
message: globals.confirmDeleteThing.replace('#thing#', $localize`:@@uploadedFile:Uploaded File`),
|
|
icon: 'ui-icon-warning',
|
|
accept: () => {
|
|
this.jobSvc.deleteAppFile({ appId: appFile.id }).subscribe((data) => {
|
|
if (data['appId']) {
|
|
this.uploadedFiles = this.uploadedFiles.filter(it => it.id !== data['appId']);
|
|
}
|
|
this.updateTotalCoverage();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
assignJob() {
|
|
if (!this.job) {
|
|
return;
|
|
}
|
|
|
|
const assignment = <jobActions.AssignInfo>{
|
|
jobId: this.job._id,
|
|
dlOp: this.selectedItem.dlOp,
|
|
avUsers: this.srcUsers,
|
|
asUsers: this.tarUsers
|
|
};
|
|
this.store.dispatch(new jobActions.Assign(assignment));
|
|
}
|
|
|
|
downloadAppfile(data) {
|
|
this.jobSvc.downloadAppFile(data.savedName).subscribe(
|
|
(res) => {
|
|
try {
|
|
saveAs(res, data.name);
|
|
} catch (error) {
|
|
alert('Sorry. Your browser does not support this feature !');
|
|
}
|
|
});
|
|
}
|
|
|
|
canDeactivate(): boolean | Promise<boolean> {
|
|
if (this.mode === MODE.NEW || this.mode === MODE.CLONE) {
|
|
return true;
|
|
}
|
|
|
|
let skipOps;
|
|
if (!this.selectedItem.loadOp.capacity) {
|
|
skipOps = ['loadOp'];
|
|
}
|
|
const changed = !(Utils.deepEquals(this._job, this.selectedItem, skipOps));
|
|
|
|
if (changed) {
|
|
return new Promise((resolve, reject) => {
|
|
this.confirmSvc.confirm({
|
|
message: globals.confirmSaveJobMsg,
|
|
accept: () => {
|
|
this.store.dispatch(new jobActions.Update(<jobActions.UpdateJobOps>{ job: this.selectedItem, updateItems: false }));
|
|
resolve(true);
|
|
},
|
|
reject: () => {
|
|
resolve(true);
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
goBackToList() {
|
|
this.route.queryParams.subscribe(params => {
|
|
const previous = params['previous'];
|
|
if (previous && previous == 'invoice') {
|
|
this.location.back();
|
|
} else {
|
|
this.router.navigate(['../', { id: this.job._id }]);
|
|
}
|
|
});
|
|
}
|
|
|
|
private resetJob() {
|
|
this.selectedItem._id = 0;
|
|
this.selectedItem.name = '';
|
|
this.selectedItem.status = 0;
|
|
this.selectedItem.endDate = null;
|
|
if (this.selectedItem.rptOp) {
|
|
this.selectedItem.rptOp.coverage = 0;
|
|
}
|
|
}
|
|
|
|
private resetNewEntities() {
|
|
this.addingNewPilot = false;
|
|
this.addingNewProduct = false;
|
|
this.addingNewVehicle = false;
|
|
}
|
|
|
|
deleteAppProduct(prod) {
|
|
if (Utils.isEmptyArray(this.selectedItem.products)) {
|
|
return;
|
|
}
|
|
this.selectedItem.products = this.selectedItem.products.filter(c => c.product._id !== prod.product._id);
|
|
}
|
|
|
|
addAppProduct() {
|
|
if (!this.curProduct) {
|
|
return;
|
|
}
|
|
|
|
if (!this.prodRate) {
|
|
this.prodRate = 10;
|
|
}
|
|
|
|
if (!Utils.isEmptyArray(this.selectedItem.products)) {
|
|
const items = this.selectedItem.products.filter(p => p.product._id === this.curProduct._id);
|
|
if (items && items.length && (items[0].product != this.curProduct || items[0].rate != this.prodRate || items[0].unit != this.prodUnit)) {
|
|
items[0].product = cloneDeep(this.curProduct);
|
|
items[0].rate = this.prodRate;
|
|
items[0].unit = this.prodUnit;
|
|
return; // Update the added product only
|
|
}
|
|
}
|
|
|
|
const newAppProd = <AppProduct>{
|
|
product: cloneDeep(this.curProduct),
|
|
rate: NumUtils.round(this.prodRate, 2),
|
|
unit: this.prodUnit
|
|
};
|
|
|
|
this.selectedItem.products = Utils.isEmptyArray(this.selectedItem.products) ? [newAppProd] : [...this.selectedItem.products, newAppProd];
|
|
}
|
|
|
|
validunit(unit) {
|
|
return this.prodUnits && this.prodUnits.length ? this.prodUnits.filter(u => u.value === unit).length : true;
|
|
}
|
|
|
|
private showDupResult(sameNum: number) {
|
|
if (sameNum) {
|
|
this.confirmSvc.confirm({
|
|
header: globals.addResult,
|
|
key: 'okOnly',
|
|
message: `${sameNum} ${sameNum > 1 ? globals.dupAreasMsg : globals.dupAreaMsg}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
showLoadRptDlg() {
|
|
this.loadSheetDlgOn = !this.loadSheetDlgOn;
|
|
const job = this.selectedItem;
|
|
const area = +job.loadOp.area ? +job.loadOp.area : UnitUtils.haToArea(job.ttSprArea, job.measureUnit);
|
|
job.loadOp.area = NumUtils.fixedTo(area, 2);
|
|
this.updateLoads();
|
|
}
|
|
|
|
ttAreaChanged(val) {
|
|
this.selectedItem.loadOp.area = NumUtils.fixedTo(val, 2);
|
|
this.updateLoads();
|
|
}
|
|
|
|
afterTTAreaChange(val) {
|
|
if (val <= 0.0) {
|
|
this.selectedItem.loadOp.area = NumUtils.fixedTo(UnitUtils.haToArea(this.selectedItem.ttSprArea, this.selectedItem.measureUnit), 2);
|
|
}
|
|
}
|
|
|
|
ttCapacityChanged(val) {
|
|
this.selectedItem.loadOp.capacity = NumUtils.fixedTo(val, 2);
|
|
this.updateLoads();
|
|
}
|
|
|
|
loadTypeChanged(e) {
|
|
this.updateLoads();
|
|
}
|
|
|
|
private updateLoads() {
|
|
const job = this.selectedItem;
|
|
job.loadOp.loads = this.computeLoads(+job.appRate, +job.appRateUnit, +job.loadOp.area, +job.loadOp.capacity);
|
|
}
|
|
|
|
private computeLoads(appRate: number, rateUnit: number, area: number, capacity: number): number {
|
|
if (!appRate || !area || !capacity) {
|
|
return 0;
|
|
}
|
|
const _apprate = rateUnit === 0 ? appRate * 0.0078125 : appRate;
|
|
return Math.ceil(area / (capacity / _apprate));
|
|
}
|
|
|
|
canPreviewLoadRpt() {
|
|
let ok = true;
|
|
if (!this.selectedItem.loadOp || !this.selectedItem.loadOp.date || !+this.selectedItem.loadOp.area || !+this.selectedItem.loadOp.capacity) {
|
|
ok = false;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
preViewLoadRpt() {
|
|
if (!this.selectedItem.loadOp.area) {
|
|
this.selectedItem.loadOp.area = NumUtils.fixedTo(UnitUtils.haToArea(this.selectedItem.ttSprArea, this.selectedItem.measureUnit), 2);
|
|
}
|
|
|
|
const options = {
|
|
jobId: this.job._id,
|
|
loadOp: <LoadOption>this.selectedItem.loadOp,
|
|
lang: this.authSvc.locale
|
|
};
|
|
|
|
this.jobSvc.preLoadReport(options).subscribe(
|
|
res => {
|
|
window.open(`/#report?rid=${res['rid']}&p=${res['path']}&c=${res['c']}&lang=${this.authSvc.locale}`, `LoadSheet-${this.job.name}`);
|
|
this._job['loadOp'] = this.selectedItem.loadOp;
|
|
},
|
|
null,
|
|
() => {
|
|
this.ngZone.run(() => this.loadSheetDlgOn = false);
|
|
this.gaSvc.gaEvent('REPORT', 'LOAD', 'V');
|
|
}
|
|
);
|
|
}
|
|
|
|
// invoice costing section
|
|
addCostingItem() {
|
|
if (!this.canAddCostingItem()) {
|
|
return;
|
|
}
|
|
|
|
const newItem = this.createCostingItem();
|
|
this.selectedItem.costings.items = [...this.selectedItem.costings.items, newItem];
|
|
this.calculateBillableAmount();
|
|
this.disableExistingCostingItems();
|
|
}
|
|
|
|
private canAddCostingItem(): boolean {
|
|
if (this.isCostingItemsEmpty()) {
|
|
this.setDefaultCurrency();
|
|
} else if (this.isCurrencyMismatch()) {
|
|
this.showCurrencyMismatchError();
|
|
return false;
|
|
}
|
|
return this.isCostingItemSelected();
|
|
}
|
|
|
|
private isCostingItemsEmpty(): boolean {
|
|
return this.selectedItem.costings.items.length === 0;
|
|
}
|
|
|
|
private setDefaultCurrency() {
|
|
this.selectedItem.costings.currency = this.invoiceSvc.defaultSetting.currency;
|
|
this.currencyUnit = this.invoiceSvc.defaultSetting.currency;
|
|
}
|
|
|
|
private isCurrencyMismatch(): boolean {
|
|
return this.selectedItem.costings.currency != this.invoiceSvc.defaultSetting.currency;
|
|
}
|
|
|
|
private showCurrencyMismatchError() {
|
|
this.costingErrorMsg = $localize`:@@jobCurrencyNotMatchSettingErr:This job's currency does not matched with invoice currency setting.`;
|
|
}
|
|
|
|
private createCostingItem() {
|
|
return {
|
|
item: this.costingItemForm.selCostingItem._id,
|
|
name: this.costingItemForm.selCostingItem.name,
|
|
price: this.costingItemForm.selCostingItem.price,
|
|
quantity: this.costingItemForm.quantity,
|
|
unit: this.costingItemForm.selCostingItem.unit,
|
|
};
|
|
}
|
|
|
|
private isCostingItemSelected(): boolean {
|
|
return this.costingItemForm.selCostingItem && this.costingItemForm.selCostingItem._id;
|
|
}
|
|
|
|
isAddCostingItemBtnDisabled(): boolean {
|
|
return this.costingItemForm.quantity <= 0 || !this.canEditCosting || (this.costingItems?.length === 0) || this.isAllCostingItemsDisabled();
|
|
}
|
|
|
|
deleteCostingItem(costingItem) {
|
|
this.selectedItem.costings.items = this.selectedItem.costings?.items?.filter(item => item.item != costingItem.item) || [];
|
|
this.calculateBillableAmount();
|
|
this.disableExistingCostingItems();
|
|
}
|
|
|
|
calculateBillableAmount() {
|
|
this.selectedItem.costings.billableAmount = Utils.arraySum(this.selectedItem.costings.items.map(item => (+item.price * +item.quantity))).roundToDecimalPlace(2);
|
|
}
|
|
|
|
createInvoice() {
|
|
if (this.isCurrencyMismatch()) {
|
|
this.showCurrencyMismatchError();
|
|
return;
|
|
}
|
|
this.store.dispatch(new jobActions.Select(this.job));
|
|
this.router.navigate(['/invoices/edit/0']);
|
|
}
|
|
|
|
viewInvoice() {
|
|
this.router.navigate([`/invoices/detail/${this.selectedItem.invoiceId}`]);
|
|
}
|
|
|
|
assignQuantity(evt) {
|
|
const value = evt.value;
|
|
if (value > this.MAX_VALUE) {
|
|
this.costingItemForm.quantity = this.MAX_VALUE;
|
|
} else if (value < this.MIN_VALUE) {
|
|
this.costingItemForm.quantity = this.MIN_VALUE;
|
|
} else {
|
|
this.costingItemForm.quantity = value;
|
|
}
|
|
}
|
|
|
|
isAllCostingItemsDisabled(): boolean {
|
|
return this.costingItems?.every(costingItem => costingItem.disabled);
|
|
}
|
|
|
|
private disableExistingCostingItems() {
|
|
this.costingItems = this.costingItems.map(costingItem => ({
|
|
...costingItem,
|
|
disabled: this.selectedItem.costings?.items?.some(item => item.item === costingItem.value._id)
|
|
}));
|
|
|
|
this.costingItemForm.selCostingItem = this.getFirstEnabledCostingItem();
|
|
}
|
|
|
|
private getFirstEnabledCostingItem() {
|
|
return this.costingItems?.find(costingItem => !costingItem.disabled)?.value;
|
|
}
|
|
|
|
isAddingNew() {
|
|
return this.addingNewPilot || this.addingNewVehicle || this.addingNewProduct || this.addingNewCropJob;
|
|
}
|
|
|
|
onAddingNewCropJobEvt(evt) {
|
|
this.addingNewCropJob = evt == 1;
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
super.ngOnDestroy();
|
|
}
|
|
}
|