In progress Sales import and Recipe import.
First I will finish Sales Import. Then move on to recipe import
This commit is contained in:
1 parent
75ccbdc6f9
commit
b425b299bf
50 files changed
+5306
-123
No files matched your search
@@ -0,0 +1,169 @@
|
||||
import { DatePipe, DecimalPipe } from '@angular/common';
|
||||
import { httpResource } from '@angular/common/http';
|
||||
import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core';
|
||||
import { form as createForm, FormField, FormRoot } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import moment from 'moment';
|
||||
|
||||
import { ErrorStateComponent } from '../shared/error-state/error-state.component';
|
||||
import { SkeletonLoaderComponent } from '../shared/skeleton-loader/skeleton-loader.component';
|
||||
import { DayPlan, ImportRequest, MappingEntry, PreviewResponse, SaleLine, VoucherPlan } from './sales-import';
|
||||
import { SalesImportService } from './sales-import.service';
|
||||
|
||||
interface SalesImportFormData {
|
||||
startDate: Date;
|
||||
finishDate: Date;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-sales-import',
|
||||
templateUrl: './sales-import.component.html',
|
||||
styleUrls: ['./sales-import.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
FormField,
|
||||
FormRoot,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatDatepickerModule,
|
||||
MatOptionModule,
|
||||
MatSelectModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatExpansionModule,
|
||||
MatTableModule,
|
||||
MatTooltipModule,
|
||||
MatSnackBarModule,
|
||||
DatePipe,
|
||||
DecimalPipe,
|
||||
SkeletonLoaderComponent,
|
||||
ErrorStateComponent,
|
||||
],
|
||||
})
|
||||
export class SalesImportComponent {
|
||||
private ser = inject(SalesImportService);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
|
||||
model = signal<SalesImportFormData>({
|
||||
startDate: moment().startOf('month').toDate(),
|
||||
finishDate: moment().toDate(),
|
||||
});
|
||||
|
||||
form = createForm(this.model);
|
||||
|
||||
previewRequest = signal<ImportRequest | null>(null);
|
||||
previewResource = httpResource<PreviewResponse>(() => {
|
||||
const request = this.previewRequest();
|
||||
return request === null ? undefined : { url: '/api/sales-import/preview', method: 'POST', body: request };
|
||||
});
|
||||
|
||||
error = computed(() => this.previewResource.error());
|
||||
loading = computed(() => this.previewResource.isLoading());
|
||||
preview = computed(() => this.previewResource.value() ?? null);
|
||||
|
||||
days = computed(() => this.preview()?.days ?? []);
|
||||
costCentres = computed(() => this.preview()?.costCentres ?? []);
|
||||
mappingEntries = computed(() => this.preview()?.mapping ?? []);
|
||||
newProducts = computed(() => this.preview()?.newProducts ?? []);
|
||||
unmappedCategories = computed(
|
||||
() => this.mappingEntries().filter((entry) => this.mappingSelection()[entry.saleCategoryId] == null).length,
|
||||
);
|
||||
|
||||
canImport = computed(() => this.days().length > 0 && this.unmappedCategories() === 0);
|
||||
|
||||
mappingSelection = signal<Record<string, string | null>>({});
|
||||
importing = signal(false);
|
||||
expanded = signal<DayPlan | null>(null);
|
||||
|
||||
displayedColumns = ['expand', 'businessDate', 'action', 'voucherCount', 'amount'];
|
||||
lineColumns = ['productName', 'quantity', 'rate', 'taxRate', 'discount', 'amount', 'happyHour'];
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const response = this.preview();
|
||||
if (response) {
|
||||
this.mappingSelection.set(
|
||||
Object.fromEntries(response.mapping.map((entry) => [entry.saleCategoryId, entry.costCentre?.id ?? null])),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show(): void {
|
||||
const model = this.model();
|
||||
this.previewRequest.set({
|
||||
startDate: moment(model.startDate).format('YYYY-MM-DD'),
|
||||
finishDate: moment(model.finishDate).format('YYYY-MM-DD'),
|
||||
});
|
||||
}
|
||||
|
||||
onMappingChange(saleCategoryId: string, costCentreId: string | null): void {
|
||||
this.mappingSelection.update((current) => ({ ...current, [saleCategoryId]: costCentreId }));
|
||||
}
|
||||
|
||||
saveMapping(): void {
|
||||
const entries: MappingEntry[] = this.mappingEntries();
|
||||
const selection = this.mappingSelection();
|
||||
const mapping = entries.map((entry) => ({
|
||||
saleCategoryId: entry.saleCategoryId,
|
||||
costCentreId: selection[entry.saleCategoryId] ?? null,
|
||||
}));
|
||||
this.ser.saveMapping({ mapping }).subscribe({
|
||||
next: () => {
|
||||
this.snackBar.open('Sale category mapping saved', 'Close', { duration: 3000 });
|
||||
this.show();
|
||||
},
|
||||
error: (err) => this.snackBar.open('Error saving mapping: ' + err.message, 'Close', { duration: 5000 }),
|
||||
});
|
||||
}
|
||||
|
||||
import(): void {
|
||||
const model = this.model();
|
||||
const request: ImportRequest = {
|
||||
startDate: moment(model.startDate).format('YYYY-MM-DD'),
|
||||
finishDate: moment(model.finishDate).format('YYYY-MM-DD'),
|
||||
};
|
||||
this.importing.set(true);
|
||||
this.ser.execute(request).subscribe({
|
||||
next: (result) => {
|
||||
this.importing.set(false);
|
||||
this.snackBar.open(result.message, 'Close', { duration: 5000 });
|
||||
this.show();
|
||||
},
|
||||
error: (err) => {
|
||||
this.importing.set(false);
|
||||
this.snackBar.open('Import failed: ' + (err.error?.detail ?? err.message), 'Close', { duration: 8000 });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
toggleExpanded(day: DayPlan): void {
|
||||
this.expanded.update((current) => (current === day ? null : day));
|
||||
}
|
||||
|
||||
actionClass(action: string): string {
|
||||
return `action action-${action}`;
|
||||
}
|
||||
|
||||
trackDay(_index: number, day: DayPlan): string {
|
||||
return day.businessDate;
|
||||
}
|
||||
|
||||
trackVoucher(_index: number, voucher: VoucherPlan): string {
|
||||
return `${voucher.saleCategoryId}-${voucher.costCentre.id}`;
|
||||
}
|
||||
|
||||
trackLine(_index: number, line: SaleLine): string {
|
||||
return `${line.skuId ?? line.productName}-${line.rate}-${line.isHappyHour}`;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user