tin-spa 20.3.0 → 20.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -662,9 +662,20 @@ class DetailsDialogProcessor {
662
662
  transformStepUrl(updatedDetailsConfig.stepConfig);
663
663
  }
664
664
  function transformLoadUrl(tableConfig) {
665
- if (tableConfig.loadAction && tableConfig.loadCriteria && tableConfig.loadIDField) {
665
+ if (!tableConfig.loadAction)
666
+ return;
667
+ // Changed: Check if URL contains {propertyName} placeholders
668
+ if (tableConfig.loadAction.url.includes('{')) {
669
+ tableConfig.loadAction.url = tableConfig.loadAction.url.replace(/\{(\w+)\}/g, (match, propName) => {
670
+ const value = updatedDetailsConfig.details?.[propName];
671
+ return value !== undefined && value !== null ? String(value) : match;
672
+ });
673
+ console.log("Transformed (placeholder)", tableConfig.loadAction.url);
674
+ }
675
+ else if (tableConfig.loadCriteria && tableConfig.loadIDField) {
676
+ // Changed: Fallback to existing loadCriteria/loadIDField approach
666
677
  tableConfig.loadAction.url = `${tableConfig.loadAction.url.split('/')[0]}/${tableConfig.loadCriteria}/${updatedDetailsConfig.details[tableConfig.loadIDField]}`;
667
- console.log("Transformed", tableConfig.loadAction.url);
678
+ console.log("Transformed (criteria)", tableConfig.loadAction.url);
668
679
  }
669
680
  else {
670
681
  console.log("NOT Transformed");
@@ -672,8 +683,17 @@ class DetailsDialogProcessor {
672
683
  }
673
684
  function transformStepUrl(stepConfig) {
674
685
  if (stepConfig.loadAction && stepConfig.loadIDField) {
675
- // let heroId = updatedDetailsConfig.details[updatedDetailsConfig.heroField];
676
- stepConfig.loadAction.url = `${stepConfig.loadAction.url.replace('{id}', updatedDetailsConfig.details[stepConfig.loadIDField])}`;
686
+ // Changed: Support generic {propertyName} placeholders
687
+ if (stepConfig.loadAction.url.includes('{')) {
688
+ stepConfig.loadAction.url = stepConfig.loadAction.url.replace(/\{(\w+)\}/g, (match, propName) => {
689
+ const value = updatedDetailsConfig.details?.[propName];
690
+ return value !== undefined && value !== null ? String(value) : match;
691
+ });
692
+ }
693
+ else {
694
+ // Fallback to legacy {id} replacement for backward compatibility
695
+ stepConfig.loadAction.url = `${stepConfig.loadAction.url.replace('{id}', updatedDetailsConfig.details[stepConfig.loadIDField])}`;
696
+ }
677
697
  // console.log("Transformed step URL", stepConfig.loadAction.url);
678
698
  }
679
699
  }
@@ -744,7 +764,16 @@ class DetailsDialogProcessor {
744
764
  static loadDetailsFromUrl(detailsConfig, formConfig) {
745
765
  let action;
746
766
  if (formConfig.loadAction) {
747
- if (detailsConfig.heroField && detailsConfig.details) {
767
+ // Changed: Check if URL contains {propertyName} placeholders first - takes priority over hero field
768
+ if (formConfig.loadAction.url.includes('{') && detailsConfig.details) {
769
+ const transformedUrl = formConfig.loadAction.url.replace(/\{(\w+)\}/g, (match, propName) => {
770
+ const value = detailsConfig.details?.[propName];
771
+ return value !== undefined && value !== null ? String(value) : match;
772
+ });
773
+ action = { url: transformedUrl };
774
+ console.log("Transformed form URL (placeholder)", transformedUrl);
775
+ }
776
+ else if (detailsConfig.heroField && detailsConfig.details) {
748
777
  // console.log("DetailsConfig Hero ID Field " + detailsConfig.heroField);
749
778
  action = { url: `${formConfig.loadAction.url}/${detailsConfig.details[detailsConfig.heroField]}` };
750
779
  }
@@ -791,6 +820,19 @@ var InvoiceStatus;
791
820
  InvoiceStatus[InvoiceStatus["Discarded"] = 3] = "Discarded";
792
821
  InvoiceStatus[InvoiceStatus["Paying"] = 4] = "Paying"; // Changed: Added Paying status for partially paid invoices
793
822
  })(InvoiceStatus || (InvoiceStatus = {}));
823
+ // Added: Invoice item type enum - mirrors backend InvoiceItemType
824
+ var InvoiceItemType;
825
+ (function (InvoiceItemType) {
826
+ InvoiceItemType[InvoiceItemType["General"] = 0] = "General";
827
+ InvoiceItemType[InvoiceItemType["Product"] = 1] = "Product";
828
+ InvoiceItemType[InvoiceItemType["Service"] = 2] = "Service";
829
+ })(InvoiceItemType || (InvoiceItemType = {}));
830
+ // Transaction timing enum - separates WHEN payment occurs from HOW
831
+ var TransactionTiming;
832
+ (function (TransactionTiming) {
833
+ TransactionTiming[TransactionTiming["Immediate"] = 0] = "Immediate";
834
+ TransactionTiming[TransactionTiming["Deferred"] = 1] = "Deferred"; // Payment occurs later - creates AR/AP
835
+ })(TransactionTiming || (TransactionTiming = {}));
794
836
  // Inventory receipt status tracking enum - mirrors backend
795
837
  var InventoryReceiptStatus;
796
838
  (function (InventoryReceiptStatus) {
@@ -1498,6 +1540,7 @@ class DataServiceLib {
1498
1540
  this.capInventoryDashboard = new CapItem;
1499
1541
  this.capInventoryStock = new CapItem; // Changed: Added new cap item for inventory stock view
1500
1542
  this.capProducts = new CapItem;
1543
+ this.capServiceItems = new CapItem; // Added: Capability for service items
1501
1544
  this.capBundleProducts = new CapItem; // Added: Capability for bundle products
1502
1545
  this.capInventoryItems = new CapItem;
1503
1546
  this.capPurchaseOrders = new CapItem;
@@ -2228,9 +2271,9 @@ class DataServiceLib {
2228
2271
  this.capInventory.name = "cap34";
2229
2272
  this.capInventory.display = "Inventory";
2230
2273
  this.capInventory.icon = "inventory";
2231
- this.capInventory.capSubItems = [this.capSales, this.capInventoryReceipts, this.capProducts, this.capBundleProducts, this.capInventoryStock, this.capInventoryTransactions, this.capInventoryItems,
2274
+ this.capInventory.capSubItems = [this.capSales, this.capInventoryReceipts, this.capProducts, this.capServiceItems, this.capBundleProducts, this.capInventoryStock, this.capInventoryTransactions, this.capInventoryItems,
2232
2275
  this.capPurchaseOrders, this.capSalesOrders, this.capRequisitions, this.capInventoryAdjustments,
2233
- this.capInventoryReturns, this.capRequisitionReturns, this.capProductionRecipes, this.capProductionOrders, this.capInventoryDashboard];
2276
+ this.capInventoryReturns, this.capRequisitionReturns, this.capProductionRecipes, this.capProductionOrders, this.capInventoryDashboard]; // Changed: Added capServiceItems to inventory menu
2234
2277
  this.capInventoryDashboard.name = "cap35";
2235
2278
  this.capInventoryDashboard.display = "Dashboard";
2236
2279
  this.capInventoryDashboard.link = "home/admin/inventory-dashboard";
@@ -2243,6 +2286,10 @@ class DataServiceLib {
2243
2286
  this.capProducts.display = "Products";
2244
2287
  this.capProducts.link = "home/admin/inventory-products";
2245
2288
  this.capProducts.icon = "category";
2289
+ this.capServiceItems.name = "cap59"; // Added: Service items capability
2290
+ this.capServiceItems.display = "Services";
2291
+ this.capServiceItems.link = "home/admin/inventory-service-items";
2292
+ this.capServiceItems.icon = "build";
2246
2293
  this.capBundleProducts.name = "cap57"; // Added: Bundle products capability
2247
2294
  this.capBundleProducts.display = "Bundle Products";
2248
2295
  this.capBundleProducts.link = "home/admin/bundle-products";
@@ -2670,7 +2717,7 @@ class AccountingService {
2670
2717
  loadAction: { url: 'accounts/id' },
2671
2718
  heroField: 'accountID',
2672
2719
  };
2673
- this.accountCreateButton = { name: 'create', display: 'Create', dialog: true, action: { url: 'accounts?action=create', method: 'post' } };
2720
+ this.accountCreateButton = { name: 'create', display: 'Create Account', dialog: true, action: { url: 'accounts?action=create', method: 'post' } };
2674
2721
  this.finAccounEditButton = { name: 'edit', dialog: true, action: { url: 'accounts?action=edit', method: 'post' } };
2675
2722
  this.accountBaseDetailsConfig = {
2676
2723
  formConfig: this.accountFormConfig,
@@ -2708,7 +2755,6 @@ class AccountingService {
2708
2755
  showFilter: false,
2709
2756
  minColumns: ['paymentDate', 'amount', 'methodName'],
2710
2757
  columns: [
2711
- { name: 'invoicePaymentID', type: 'number', alias: 'ID', hiddenCondition: () => true },
2712
2758
  { name: 'paymentDate', type: 'date', alias: 'Date' },
2713
2759
  { name: 'amount', type: 'money', alias: 'Amount' },
2714
2760
  { name: 'methodName', type: 'text', alias: 'Method' },
@@ -2719,9 +2765,21 @@ class AccountingService {
2719
2765
  action: { url: 'invoicepayments/migrate', method: 'post',
2720
2766
  successMessage: 'Migration completed successfully' },
2721
2767
  confirm: { message: 'This will migrate existing invoice payments from the legacy PaidAmount field to the new InvoicePayments table. Continue?' }
2722
- }
2768
+ },
2769
+ { name: 'edit', dialog: true, action: { url: 'invoicepayments?action=edit', method: 'post' }, visible: x => x.status != InvoiceStatus.Paid }, // Changed: Added edit button, hide on Paid invoices
2770
+ { name: 'delete', inDialog: true, icon: { name: 'delete', color: 'red' }, action: { url: 'invoicepayments?action=delete', method: 'post', successMessage: 'Payment deleted' }, confirm: { message: 'Delete this payment? The linked financial transaction will be reversed.' }, visible: x => x.status != InvoiceStatus.Paid } // Changed: Added delete button with reversal warning, hide on Paid invoices
2723
2771
  ],
2724
- loadAction: { url: 'invoicepayments/x/x' }, loadCriteria: 'invoice', loadIDField: 'invoiceID'
2772
+ loadAction: { url: 'invoicepayments/x/x' }, loadCriteria: 'invoice', loadIDField: 'invoiceID',
2773
+ formConfig: {
2774
+ title: 'Edit Payment',
2775
+ fields: [
2776
+ { name: 'paymentDate', type: 'date', alias: 'Payment Date', required: true },
2777
+ { name: 'method', type: 'select', alias: 'Payment Method', required: true, loadAction: { url: 'invoicepayments/list/methods' } },
2778
+ { name: 'amount', type: 'money', alias: 'Amount', required: true },
2779
+ { name: 'reference', type: 'text', alias: 'Reference' }
2780
+ ],
2781
+ loadAction: { url: 'invoicepayments/id' }
2782
+ }
2725
2783
  };
2726
2784
  // Invoice form configuration with customer and status fields
2727
2785
  this.invoiceFormConfig = {
@@ -2742,6 +2800,36 @@ class AccountingService {
2742
2800
  loadAction: { url: 'invoices/id' },
2743
2801
  heroField: 'invoiceID',
2744
2802
  };
2803
+ // Invoice items form configuration
2804
+ this.invoiceItemsFormConfig = {
2805
+ title: 'Invoice Item',
2806
+ fields: [
2807
+ { name: 'itemType', type: 'select', required: true, alias: 'Item Type', loadAction: { url: 'invoiceitems/list/item-types' }, defaultValue: 0 }, // Added: Item type selector
2808
+ { name: 'productID', type: 'select', alias: 'Product', loadAction: { url: 'products/list/invoice-items' },
2809
+ hiddenCondition: x => x.itemType !== 1, // Added: Product selector - visible only when itemType = Product (1)
2810
+ onSelectChange: (selectedId, formData, option) => {
2811
+ if (option) {
2812
+ formData.description = option.description || option.name;
2813
+ formData.unitPrice = option.unitPrice;
2814
+ }
2815
+ }
2816
+ },
2817
+ { name: 'serviceItemID', type: 'select', alias: 'Service', loadAction: { url: 'serviceitems/list/active' },
2818
+ hiddenCondition: x => x.itemType !== 2, // Added: Service selector - visible only when itemType = Service (2)
2819
+ onSelectChange: (selectedId, formData, option) => {
2820
+ if (option) {
2821
+ formData.description = option.description || option.name;
2822
+ formData.unitPrice = option.unitPrice;
2823
+ }
2824
+ }
2825
+ },
2826
+ { name: 'description', type: 'text', required: true, alias: 'Description', span: true }, // Changed: Description field (backend will populate for Product/Service)
2827
+ { name: 'quantity', type: 'number', required: true, defaultValue: 1 },
2828
+ { name: 'unitPrice', type: 'money', alias: 'Unit Price', required: true }, // Changed: Unit price field (backend will populate for Product/Service)
2829
+ ],
2830
+ loadAction: { url: 'invoiceitems/id/{invoiceItemID}' },
2831
+ heroField: 'invoiceItemID',
2832
+ };
2745
2833
  // Invoice items table for manual line item entry
2746
2834
  this.invoiceItemsTableConfig = {
2747
2835
  tabTitle: 'Invoice Items',
@@ -2759,20 +2847,17 @@ class AccountingService {
2759
2847
  { name: 'create', display: 'Add Item', dialog: true, action: { url: 'invoiceitems?action=create', method: 'post' },
2760
2848
  disabled: x => x.status != InvoiceStatus.Draft
2761
2849
  },
2762
- { name: 'edit', dialog: true, action: { url: 'invoiceitems?action=edit', method: 'post' }, disabled: x => x.status != InvoiceStatus.Draft },
2763
- { name: 'delete', inDialog: true, icon: { name: 'delete', color: 'red' }, action: { url: 'invoiceitems?action=delete', method: 'post', successMessage: 'Deleted' }, confirm: { message: 'Delete this item?' }, disabled: x => x.status != InvoiceStatus.Draft }
2850
+ { name: 'edit', dialog: true, action: { url: 'invoiceitems?action=edit', method: 'post' },
2851
+ disabled: x => x.status != InvoiceStatus.Draft
2852
+ },
2853
+ { name: 'delete', inDialog: true, icon: { name: 'delete', color: 'red' }, action: { url: 'invoiceitems?action=delete', method: 'post',
2854
+ successMessage: 'Deleted' },
2855
+ confirm: { message: 'Delete this item?' },
2856
+ disabled: x => x.status != InvoiceStatus.Draft
2857
+ },
2764
2858
  ],
2765
2859
  loadAction: { url: 'invoiceitems/x/x' }, loadCriteria: 'invoice', loadIDField: 'invoiceID',
2766
- formConfig: {
2767
- title: 'Invoice Item',
2768
- fields: [
2769
- { name: 'description', type: 'text', required: true, span: true },
2770
- { name: 'quantity', type: 'number', required: true, defaultValue: 1 },
2771
- { name: 'unitPrice', type: 'number', alias: 'Unit Price', required: true },
2772
- { name: 'amount', type: 'number', readonly: true, hideOnCreate: true }
2773
- ],
2774
- loadAction: { url: 'invoiceitems/id' }
2775
- }
2860
+ formConfig: this.invoiceItemsFormConfig // Changed: Reference extracted form config
2776
2861
  };
2777
2862
  // Invoice action buttons
2778
2863
  this.invoiceRecordPaymentButton = { name: 'record-payment', display: 'Record Payment', dialog: true, icon: { name: 'payment', color: 'blue' },
@@ -2780,7 +2865,8 @@ class AccountingService {
2780
2865
  visible: x => (x.status == InvoiceStatus.Submitted || x.status == InvoiceStatus.Paying) && x.outstandingAmount > 0 // Changed: Allow on both Submitted and Paying
2781
2866
  }; // Changed: Use detailsConfig to open payment form dialog
2782
2867
  this.invoiceDiscardButton = { name: 'discard', inDialog: true, display: 'Discard', icon: { name: 'close', color: 'red' },
2783
- action: { url: 'invoices?action=discard', method: 'post', successMessage: 'Discarded' }, confirm: { message: 'Invoice will be marked as cancelled?' },
2868
+ action: { url: 'invoices?action=discard', method: 'post', successMessage: 'Discarded' },
2869
+ confirm: { message: 'Invoice will be marked as cancelled?' },
2784
2870
  visible: x => x.status == InvoiceStatus.Draft || x.status == InvoiceStatus.Submitted // Changed: Only allow on Draft and Submitted
2785
2871
  };
2786
2872
  this.invoiceSubmitButton = { name: 'submit', inDialog: true, display: 'Submit', icon: { name: 'send', },
@@ -2789,8 +2875,9 @@ class AccountingService {
2789
2875
  visible: x => x.status == InvoiceStatus.Draft,
2790
2876
  disabled: x => x.totalAmount == 0
2791
2877
  };
2792
- this.invoiceEditButton = { name: 'edit', dialog: true, action: { url: 'invoices?action=edit', method: 'post', }, confirm: { message: 'Proceed ?' },
2793
- visible: x => x.status == InvoiceStatus.Draft || x.status == InvoiceStatus.Submitted // Changed: Disable on Paying and Paid
2878
+ this.invoiceEditButton = { name: 'edit', dialog: true, action: { url: 'invoices?action=edit', method: 'post', },
2879
+ confirm: { message: 'Proceed ?' },
2880
+ visible: x => x.status == InvoiceStatus.Draft // Changed: Draft only
2794
2881
  };
2795
2882
  this.invoiceDownloadButton = { name: 'pdf', display: 'Download PDF', inDialog: true, icon: { name: 'picture_as_pdf', color: 'red' } };
2796
2883
  // Invoice details dialog with items, payments history, and action buttons
@@ -2820,7 +2907,7 @@ class AccountingService {
2820
2907
  collapseButtons: true,
2821
2908
  greyOut: x => x.status == InvoiceStatus.Paid,
2822
2909
  columns: [
2823
- { name: 'invoiceNumber', type: 'text', alias: 'Invoice #' },
2910
+ { name: 'invoiceNumber', type: 'button', alias: 'Invoice #', detailsConfig: this.invoiceDetailsDialogConfig },
2824
2911
  { name: 'customerName', type: 'text', alias: 'Customer' },
2825
2912
  { name: 'invoiceDate', type: 'date' },
2826
2913
  { name: 'dueDate', type: 'date', alias: 'Due Date' },
@@ -2840,14 +2927,16 @@ class AccountingService {
2840
2927
  { name: 'outstandingAmount', type: 'money', alias: 'Outstanding' }
2841
2928
  ],
2842
2929
  buttons: [
2843
- { name: 'create', display: 'Create', dialog: true, action: { url: 'invoices?action=create', method: 'post' }, onSuccessButton: this.invoiceViewButton },
2844
- { name: 'view', dialog: true, detailsConfig: this.invoiceDetailsDialogConfig },
2845
- this.invoiceRecordPaymentButton,
2930
+ { name: 'create', display: 'Create', dialog: true, onSuccessButton: this.invoiceViewButton,
2931
+ action: { url: 'invoices?action=create', method: 'post' }
2932
+ },
2933
+ // { name: 'view', dialog: true, detailsConfig: this.invoiceDetailsDialogConfig },
2846
2934
  this.invoiceSubmitButton,
2935
+ this.invoiceRecordPaymentButton,
2847
2936
  // this.invoicePayButton,
2848
2937
  this.invoiceDiscardButton,
2849
- this.invoiceEditButton,
2850
- this.invoiceDownloadButton,
2938
+ // this.invoiceEditButton,
2939
+ // this.invoiceDownloadButton,
2851
2940
  ],
2852
2941
  loadAction: { url: 'invoices/all/x' },
2853
2942
  formConfig: this.invoiceFormConfig,
@@ -2870,9 +2959,10 @@ class AccountingService {
2870
2959
  // Added: Base aging table configuration with common properties
2871
2960
  this.agingBaseTableConfig = {
2872
2961
  showFilter: true,
2962
+ flatButtons: true,
2873
2963
  minColumns: ['invoiceNumber', 'customerName', 'outstandingAmount'],
2874
2964
  columns: [
2875
- { name: 'invoiceNumber', type: 'text', alias: 'Invoice #' },
2965
+ { name: 'invoiceNumber', type: 'text', alias: 'Invoice #', detailsConfig: this.invoiceDetailsDialogConfig },
2876
2966
  { name: 'customerName', type: 'text', alias: 'Customer' },
2877
2967
  { name: 'invoiceDate', type: 'date', alias: 'Invoice Date' },
2878
2968
  { name: 'dueDate', type: 'date', alias: 'Due Date' },
@@ -2881,7 +2971,10 @@ class AccountingService {
2881
2971
  { name: 'paidAmount', type: 'money', alias: 'Paid' },
2882
2972
  { name: 'outstandingAmount', type: 'money', alias: 'Outstanding' }
2883
2973
  ],
2884
- buttons: [{ name: 'view', dialog: true, detailsConfig: this.invoiceDetailsDialogConfig }]
2974
+ buttons: [
2975
+ { name: 'view', dialog: true, detailsConfig: this.invoiceDetailsDialogConfig },
2976
+ this.invoiceRecordPaymentButton,
2977
+ ]
2885
2978
  };
2886
2979
  //--------------------------Customer Invoices-------------------------
2887
2980
  // Customer invoice form without customerID field
@@ -3061,15 +3154,15 @@ class AccountingService {
3061
3154
  { name: 'red', condition: x => x.status == 1 },
3062
3155
  ],
3063
3156
  },
3064
- { name: 'amount', type: 'money',
3065
- color: { name: 'red', condition: x => x.reducesBalance && !x.isAggregate },
3066
- },
3067
3157
  { name: 'typeName', type: 'text', alias: 'Type' },
3068
3158
  { name: 'debitAccountName', type: 'text', alias: 'Debit Account' },
3069
3159
  { name: 'creditAccountName', type: 'text', alias: 'Credit Account' },
3160
+ { name: 'amount', type: 'money',
3161
+ color: { name: 'red', condition: x => x.reducesBalance && !x.isAggregate },
3162
+ },
3070
3163
  ],
3071
3164
  buttons: [
3072
- { name: 'create', display: 'Create', dialog: true, action: { url: 'transactions/dto?action=create', method: 'post' } },
3165
+ { name: 'create', display: 'Create Transaction', dialog: true, action: { url: 'transactions/dto?action=create', method: 'post' } },
3073
3166
  { name: 'view', dialog: true, detailsConfig: this.transactionDetailsConfig },
3074
3167
  this.transactionEditButton,
3075
3168
  this.transactionVoidButton,
@@ -3080,12 +3173,91 @@ class AccountingService {
3080
3173
  this.accountTransactionsTableConfig = {
3081
3174
  ...this.transactionsTableConfig,
3082
3175
  causeFormRefresh: true,
3176
+ minColumns: ['date', 'description', 'amount', 'runningBalance'],
3083
3177
  tabTitle: 'Account Transactions',
3084
3178
  loadAction: { url: 'transactions/account/x' }, loadCriteria: 'account', loadIDField: 'accountID',
3085
3179
  columns: [
3086
3180
  ...this.transactionsTableConfig.columns,
3087
- { name: 'runningBalance', type: 'money', alias: 'Balance' } // Added running balance column
3181
+ { name: 'runningBalance', type: 'money', alias: 'Balance' }
3182
+ ],
3183
+ };
3184
+ //--------------------------Transaction Templates-------------------------
3185
+ // Template form configuration (accountID hidden - auto-populated from context)
3186
+ this.transactionTemplateFormConfig = {
3187
+ security: { allow: [this.dataService.capTransactions] },
3188
+ title: 'Transaction Template',
3189
+ includeAudit: true,
3190
+ fields: [
3191
+ { name: 'name', type: 'text', required: true, span: true },
3192
+ { name: 'accountID', type: 'number', hidden: true }, // Auto-populated from Account Details context
3193
+ { name: 'transactionTypeID', type: 'select', alias: 'Transaction Type', required: true, detailsConfig: this.transactionTypeDetailsConfig, loadAction: { url: 'transactionTypes/list/x' } },
3194
+ { name: 'blank', type: 'blank' },
3195
+ { name: 'debitAccountID', type: 'select', alias: 'Default Debit Account', nullable: true, masterField: 'transactionTypeID', masterValueField: 'debitAccountType', masterDefaultValueField: 'defaultDebitAccountID', masterOptionValue: 'type', loadAction: { url: 'accounts/list/x' }, detailsConfig: this.accountBaseDetailsConfig },
3196
+ { name: 'creditAccountID', type: 'select', alias: 'Default Credit Account', nullable: true, masterField: 'transactionTypeID', masterValueField: 'creditAccountType', masterDefaultValueField: 'defaultCreditAccountID', masterOptionValue: 'type', loadAction: { url: 'accounts/list/x' }, detailsConfig: this.accountBaseDetailsConfig },
3197
+ { name: 'description', type: 'text', alias: 'Default Description', span: true },
3198
+ { name: 'defaultAmount', type: 'money', alias: 'Default Amount', span: true },
3199
+ ],
3200
+ loadAction: { url: 'transactiontemplates/id' },
3201
+ heroField: 'transactionTemplateID',
3202
+ };
3203
+ // Process form configuration (convert template to transaction)
3204
+ this.transactionTemplateProcessFormConfig = {
3205
+ title: 'Create Transaction from Template',
3206
+ fixedTitle: true,
3207
+ fields: [
3208
+ { name: 'transactionTemplateID', type: 'number', hidden: true },
3209
+ { name: 'name', type: 'label', alias: 'Template', readonly: true, span: true },
3210
+ { name: 'transactionTypeID', type: 'select', alias: 'Transaction Type', required: true, detailsConfig: this.transactionTypeDetailsConfig, loadAction: { url: 'transactionTypes/list/x' } },
3211
+ { name: 'date', type: 'date', required: true, defaultValue: 'now' },
3212
+ { name: 'debitAccountID', type: 'select', alias: 'Debit Account', required: true, masterField: 'transactionTypeID', masterValueField: 'debitAccountType', masterOptionValue: 'type', loadAction: { url: 'accounts/list/x' }, detailsConfig: this.accountBaseDetailsConfig },
3213
+ { name: 'creditAccountID', type: 'select', alias: 'Credit Account', required: true, masterField: 'transactionTypeID', masterValueField: 'creditAccountType', masterOptionValue: 'type', loadAction: { url: 'accounts/list/x' }, detailsConfig: this.accountBaseDetailsConfig },
3214
+ { name: 'description', type: 'text', required: true, span: true },
3215
+ { name: 'amount', type: 'money', required: true, span: true },
3216
+ ],
3217
+ };
3218
+ // Template action buttons
3219
+ this.transactionTemplateCreateButton = { name: 'create', display: 'Create Template', dialog: true, action: { url: 'transactiontemplates?action=create', method: 'post' } };
3220
+ this.transactionTemplateEditButton = { name: 'edit', dialog: true, action: { url: 'transactiontemplates?action=edit', method: 'post' } };
3221
+ // Template details config (for view/edit)
3222
+ this.transactionTemplateDetailsConfig = {
3223
+ formConfig: this.transactionTemplateFormConfig,
3224
+ heroField: 'transactionTemplateID',
3225
+ buttons: [this.transactionTemplateCreateButton, this.transactionTemplateEditButton]
3226
+ };
3227
+ // Process details config (for processing template)
3228
+ this.transactionTemplateProcessDetailsConfig = {
3229
+ formConfig: this.transactionTemplateProcessFormConfig,
3230
+ heroField: 'transactionTemplateID',
3231
+ mode: 'edit',
3232
+ buttons: [
3233
+ { name: 'process', display: 'Create Transaction', icon: { name: 'play_arrow', color: 'green' }, inDialog: true,
3234
+ action: { url: 'transactiontemplates/dto?action=process', method: 'post',
3235
+ successMessage: 'Transaction created from template' }
3236
+ }
3237
+ ]
3238
+ };
3239
+ // Templates table configuration (3rd tab in Account Details)
3240
+ this.transactionTemplatesTableConfig = {
3241
+ tabTitle: 'Transaction Templates',
3242
+ showFilter: true,
3243
+ minColumns: ['name', 'transactionTypeName', 'defaultAmountDisplay'],
3244
+ flatButtons: true,
3245
+ columns: [
3246
+ { name: 'name', type: 'button', detailsConfig: this.transactionTemplateProcessDetailsConfig },
3247
+ { name: 'transactionTypeName', type: 'text', alias: 'Type' },
3248
+ { name: 'debitAccountName', type: 'text', alias: 'Debit Account' },
3249
+ { name: 'creditAccountName', type: 'text', alias: 'Credit Account' },
3250
+ { name: 'defaultAmountDisplay', type: 'text', alias: 'Default Amount' },
3251
+ ],
3252
+ buttons: [
3253
+ this.transactionTemplateCreateButton,
3254
+ { name: 'process', display: 'Create Transaction', icon: { name: 'play_arrow', color: 'green' }, dialog: true, detailsConfig: this.transactionTemplateProcessDetailsConfig },
3255
+ { name: 'view', dialog: true, detailsConfig: this.transactionTemplateDetailsConfig },
3256
+ { name: 'edit', dialog: true, action: { url: 'transactiontemplates?action=edit', method: 'post' }, detailsConfig: this.transactionTemplateDetailsConfig }, // Changed: Inline edit button with both action and detailsConfig
3257
+ { name: 'delete', inDialog: true, icon: { name: 'delete', color: 'red' }, action: { url: 'transactiontemplates?action=delete', method: 'post', successMessage: 'Template deleted' }, confirm: { message: 'Delete this template?' } },
3088
3258
  ],
3259
+ loadAction: { url: 'transactiontemplates/account/x' }, loadCriteria: 'account', loadIDField: 'accountID',
3260
+ formConfig: this.transactionTemplateFormConfig
3089
3261
  };
3090
3262
  //--------------------------Accounts-------------------------
3091
3263
  this.accountDetailsConfig = {
@@ -3098,6 +3270,7 @@ class AccountingService {
3098
3270
  buttons: [],
3099
3271
  loadAction: { url: 'transactionTypes/account/x' }, loadCriteria: 'account', loadIDField: 'accountID'
3100
3272
  },
3273
+ { ...this.transactionTemplatesTableConfig }, // Changed: Added 3rd tab for transaction templates
3101
3274
  ]
3102
3275
  };
3103
3276
  this.accountsTableConfig = {
@@ -3716,6 +3889,47 @@ class InventoryService {
3716
3889
  loadAction: { url: 'products/bundles/x' },
3717
3890
  formConfig: this.bundleProductFormConfig
3718
3891
  };
3892
+ //--------------------------ServiceItems-------------------------
3893
+ // ServiceItem form configuration - follows Product pattern
3894
+ this.serviceItemFormConfig = {
3895
+ title: 'Service',
3896
+ includeAudit: true,
3897
+ fields: [
3898
+ { name: 'name', type: 'text', required: true, alias: 'Service Name' },
3899
+ { name: 'description', type: 'text', alias: 'Description' },
3900
+ { name: 'unitPrice', type: 'money', required: true, alias: 'Unit Price' },
3901
+ { name: 'isActive', type: 'checkbox', alias: 'Active', defaultValue: true }
3902
+ ],
3903
+ loadAction: { url: 'serviceitems/id' },
3904
+ heroField: 'serviceItemID'
3905
+ };
3906
+ this.serviceItemCreateButton = { name: 'create', display: 'Create Service', dialog: true, action: { url: 'serviceitems?action=create', method: 'post' } };
3907
+ this.serviceItemEditButton = { name: 'edit', dialog: true, action: { url: 'serviceitems?action=edit', method: 'post' } };
3908
+ this.serviceItemDeleteButton = { name: 'delete', dialog: true, action: { url: 'serviceitems?action=delete', method: 'post' } };
3909
+ this.serviceItemDetailsConfig = {
3910
+ formConfig: this.serviceItemFormConfig,
3911
+ heroField: 'serviceItemID',
3912
+ buttons: [this.serviceItemCreateButton, this.serviceItemEditButton, this.serviceItemDeleteButton]
3913
+ };
3914
+ this.serviceItemsTableConfig = {
3915
+ showFilter: true,
3916
+ flatButtons: true,
3917
+ minColumns: ['name', 'unitPrice', 'statusName'],
3918
+ columns: [
3919
+ { name: 'name', type: 'text', alias: 'Service Name' },
3920
+ { name: 'description', type: 'text', alias: 'Description' },
3921
+ { name: 'unitPrice', type: 'money', alias: 'Unit Price' },
3922
+ { name: 'statusName', type: 'text', alias: 'Status' }
3923
+ ],
3924
+ buttons: [
3925
+ this.serviceItemCreateButton,
3926
+ { name: 'view', dialog: true, detailsConfig: this.serviceItemDetailsConfig },
3927
+ this.serviceItemEditButton,
3928
+ this.serviceItemDeleteButton
3929
+ ],
3930
+ loadAction: { url: 'serviceitems/all/x' },
3931
+ formConfig: this.serviceItemFormConfig
3932
+ };
3719
3933
  //--------------------------Requisitions-------------------------
3720
3934
  this.requisitionItemFormConfig = {
3721
3935
  title: 'Requisition Item',
@@ -3807,10 +4021,10 @@ class InventoryService {
3807
4021
  };
3808
4022
  //--------------------------Purchase Orders-------------------------
3809
4023
  // Payment type options (used across forms)
4024
+ // Changed: Removed Credit option - timing now determines credit vs cash purchase
3810
4025
  this.paymentTypeOptions = [
3811
4026
  { value: 0, name: 'Cash' },
3812
- { value: 1, name: 'Bank' },
3813
- { value: 2, name: 'Credit' }
4027
+ { value: 1, name: 'Bank' }
3814
4028
  ];
3815
4029
  // PO Item form configuration
3816
4030
  this.purchaseOrderItemFormConfig = {
@@ -3994,9 +4208,15 @@ class InventoryService {
3994
4208
  { name: 'supplierID', type: 'select', required: true, alias: 'Supplier', section: 'receiptInfo',
3995
4209
  loadAction: { url: 'suppliers/list/x' }, detailsConfig: this.dataService.supplierDetailsConfig, infoMessage: 'Supplier providing the goods'
3996
4210
  },
3997
- { name: 'paymentType', type: 'select', required: true, alias: 'Payment Type', section: 'receiptInfo',
3998
- options: this.paymentTypeOptions, defaultFirstValue: true, infoMessage: 'Payment method used for this purchase' },
3999
- { name: 'supplierInvoiceNumber', type: 'text', alias: 'Supplier Invoice #', section: 'receiptInfo', infoMessage: 'Invoice number from supplier' },
4211
+ { name: 'timing', type: 'select', required: true, alias: 'Transaction Type', section: 'receiptInfo',
4212
+ loadAction: { url: 'inventoryreceipts/list/timing' }, defaultFirstValue: true,
4213
+ infoMessage: 'Cash Purchase (pay now) or Credit Purchase (pay later)'
4214
+ },
4215
+ { name: 'paymentType', type: 'select', required: true, alias: 'Payment Method', section: 'receiptInfo', defaultFirstValue: true,
4216
+ options: this.paymentTypeOptions, infoMessage: 'Payment method used for this purchase',
4217
+ hiddenCondition: x => x.timing === 1,
4218
+ requiredCondition: x => x.timing === 0
4219
+ },
4000
4220
  { name: 'receiptNumber', type: 'text', alias: 'Receipt Number', readonly: true, hideOnCreate: true, section: 'receiptInfo', infoMessage: 'Auto-generated unique receipt identifier' },
4001
4221
  { name: 'poNumber', type: 'text', alias: 'PO Number', hideOnCreate: true, section: 'receiptInfo', infoMessage: 'Linked purchase order if receiving against a PO',
4002
4222
  hiddenCondition: (row) => !row.purchaseOrderID,
@@ -4023,12 +4243,16 @@ class InventoryService {
4023
4243
  { name: 'totals', type: 'section', alias: 'Totals', hideOnCreate: true, collapsed: true },
4024
4244
  { name: 'totalAmount', type: 'money', alias: 'Total Amount', readonly: true, section: 'totals', infoMessage: 'Total value of all items' },
4025
4245
  { name: 'additionalInfo', type: 'section', alias: 'Additional Information', collapsed: true },
4026
- { name: 'notes', type: 'text', alias: 'Notes', span: true, section: 'additionalInfo', infoMessage: 'Additional notes about this receipt' }
4246
+ { name: 'notes', type: 'text', alias: 'Notes', span: true, section: 'additionalInfo', infoMessage: 'Additional notes about this receipt' },
4247
+ { name: 'supplierInvoiceNumber', type: 'text', alias: 'Supplier Invoice #', section: 'additionalInfo', infoMessage: 'Invoice number from supplier' },
4027
4248
  ],
4028
4249
  loadAction: { url: 'inventoryreceipts/id' },
4029
4250
  heroField: 'inventoryReceiptID'
4030
4251
  };
4031
- this.inventoryReceiptEditButton = { name: 'edit', dialog: true, action: { url: 'inventoryreceipts?action=edit', method: 'post' }, visible: (x) => x.status !== InventoryReceiptStatus.Completed }; // Changed: Use status enum instead of statusName
4252
+ this.inventoryReceiptEditButton = { name: 'edit', dialog: true,
4253
+ action: { url: 'inventoryreceipts?action=edit', method: 'post' },
4254
+ visible: (x) => x.status !== InventoryReceiptStatus.Completed
4255
+ };
4032
4256
  this.inventoryReceiptCompleteButton = { name: 'complete', display: 'Complete Receipt', inDialog: true, // Changed: Updated display text and action name
4033
4257
  icon: { name: 'check_circle', color: 'green' },
4034
4258
  action: { url: 'inventoryreceipts?action=complete', method: 'post', successMessage: 'Receipt Completed' }, // Changed: Updated endpoint and message
@@ -4061,7 +4285,7 @@ class InventoryService {
4061
4285
  { name: 'receiptDate', type: 'date', alias: 'Date' },
4062
4286
  { name: 'supplierName', type: 'text', alias: 'Supplier' },
4063
4287
  { name: 'productsDisplay', type: 'text', alias: 'Products' }, // Added: Display product names or count
4064
- { name: 'paymentTypeName', type: 'text', alias: 'Payment' },
4288
+ { name: 'timingName', type: 'text', alias: 'Payment' },
4065
4289
  { name: 'status', type: 'icon', alias: 'Status', detailsConfig: this.inventoryReceiptDetailsConfig, // Changed: Use status instead of statusName
4066
4290
  icons: [
4067
4291
  { name: 'article', color: '#9E9E9E', condition: (x) => x.status === InventoryReceiptStatus.Draft, tip: 'Draft' }, // Changed: Use status enum
@@ -4362,7 +4586,23 @@ class InventoryService {
4362
4586
  fields: [
4363
4587
  { name: 'saleInfo', type: 'section', alias: 'Sale Information' }, // Changed: Hide when single product mode or when editing existing sale
4364
4588
  { name: 'saleDate', type: 'date', required: true, alias: 'Sale Date', section: 'saleInfo', infoMessage: 'Date when the sale was completed' },
4365
- { name: 'customerID', type: 'select', required: true, alias: 'Customer', section: 'saleInfo', loadAction: { url: 'customers/list/x' }, detailsConfig: this.dataService.customerDetailsConfig },
4589
+ { name: 'customerID', type: 'select', required: true, alias: 'Customer', section: 'saleInfo',
4590
+ loadAction: { url: 'customers/list/x' }, detailsConfig: this.dataService.customerDetailsConfig
4591
+ },
4592
+ { name: 'timing', type: 'select', required: true, alias: 'Transaction Type', section: 'saleInfo',
4593
+ loadAction: { url: 'sales/list/timing' }, defaultFirstValue: true, infoMessage: 'Cash Sale (pay now) or Credit Sale (pay later)'
4594
+ },
4595
+ { name: 'paymentMethod', type: 'select', required: true, alias: 'Payment Method', section: 'saleInfo', defaultFirstValue: true, infoMessage: 'Method of payment used for this sale',
4596
+ requiredCondition: x => x.timing === 0,
4597
+ hiddenCondition: x => x.timing === 1,
4598
+ options: [
4599
+ { name: 'Cash', value: 0 },
4600
+ { name: 'Bank Transfer', value: 1 },
4601
+ { name: 'Mobile Money', value: 2 },
4602
+ { name: 'Card', value: 3 },
4603
+ { name: 'Complementary', value: 5 }
4604
+ ],
4605
+ },
4366
4606
  { name: 'multipleProducts', type: 'checkbox', alias: 'Multiple Products', defaultValue: false, hideOnExists: true, infoMessage: 'Check this box if you want to add multiple products to this sale' }, // Changed: Added checkbox for multiple products mode
4367
4607
  { name: 'saleType', type: 'select', required: true, alias: 'Sale Type', section: 'saleInfo', options: [{ name: 'Quick Sale', value: 0 }, { name: 'From Order', value: 1 }], defaultFirstValue: true, hideOnCreate: true, infoMessage: 'Select if this is a quick sale or created from an order' },
4368
4608
  { name: 'quickSaleItem', type: 'section', alias: 'Quick Sale Item', hiddenCondition: x => x.multipleProducts === true || x.saleID }, // Changed: Section for single product entry
@@ -4375,17 +4615,16 @@ class InventoryService {
4375
4615
  },
4376
4616
  { name: 'quantity', type: 'number', required: true, alias: 'Quantity', section: 'quickSaleItem', defaultValue: 1, hiddenCondition: x => x.multipleProducts === true, infoMessage: 'Quantity of the product' }, // Changed: Quantity field for quick sale
4377
4617
  { name: 'unitPrice', type: 'money', required: true, alias: 'Unit Price', section: 'quickSaleItem', hiddenCondition: x => x.multipleProducts === true, infoMessage: 'Price per unit' }, // Changed: Unit price field for quick sale
4378
- { name: 'paymentInfo', type: 'section', alias: 'Payment Information', collapsedCondition: x => x.saleID },
4379
- { name: 'paymentMethod', type: 'select', required: true, alias: 'Payment Method', section: 'paymentInfo', options: [{ name: 'Cash', value: 0 }, { name: 'Bank Transfer', value: 1 }, { name: 'Mobile Money', value: 2 }, { name: 'Card', value: 3 }, { name: 'Credit', value: 4 }, { name: 'Complementary', value: 5 }], defaultFirstValue: true, infoMessage: 'Method of payment used for this sale' },
4380
- { name: 'paymentReference', type: 'text', alias: 'Payment Reference', section: 'paymentInfo', infoMessage: 'Transaction reference number or payment receipt' },
4381
- { name: 'paymentStatus', type: 'select', alias: 'Payment Status', readonly: true, section: 'paymentInfo', loadAction: { url: 'sales/list/payment-status' }, hideOnCreate: true, infoMessage: 'Current payment status of the sale' },
4382
4618
  { name: 'totals', type: 'section', alias: 'Totals', collapsed: true, hideOnCreate: true },
4383
4619
  { name: 'subTotal', type: 'money', alias: 'Sub Total', readonly: true, section: 'totals', infoMessage: 'Total before tax and discounts' },
4384
4620
  { name: 'taxAmount', type: 'money', alias: 'Tax Amount', section: 'totals', infoMessage: 'Tax amount applied to the sale' },
4385
4621
  { name: 'discount', type: 'money', alias: 'Discount', section: 'totals', infoMessage: 'Discount amount applied to the sale' },
4386
4622
  { name: 'totalAmount', type: 'money', alias: 'Total Amount', readonly: true, section: 'totals', infoMessage: 'Final amount after tax and discounts' },
4387
4623
  { name: 'additionalInfo', type: 'section', alias: 'Additional Information', collapsed: true },
4388
- { name: 'notes', type: 'text', alias: 'Notes', span: true, section: 'additionalInfo', infoMessage: 'Additional notes or comments about this sale' }
4624
+ { name: 'notes', type: 'text', alias: 'Notes', span: true, section: 'additionalInfo', infoMessage: 'Additional notes or comments about this sale' },
4625
+ { name: 'paymentReference', type: 'text', alias: 'Payment Reference', section: 'additionalInfo', infoMessage: 'Transaction reference number or payment receipt',
4626
+ hiddenCondition: x => x.timing === 1
4627
+ },
4389
4628
  ],
4390
4629
  loadAction: { url: 'sales/id' },
4391
4630
  heroField: 'saleID'
@@ -4399,26 +4638,72 @@ class InventoryService {
4399
4638
  { name: 'Partial', value: 2, icon: 'payment' }
4400
4639
  ]
4401
4640
  };
4641
+ // Added: Sale payment form configuration (matches invoice payment form)
4642
+ this.salePaymentFormConfig = {
4643
+ security: { allow: [this.dataService.capSales] },
4644
+ title: 'Record Payment',
4645
+ fixedTitle: true,
4646
+ fields: [
4647
+ { name: 'saleID', type: 'number', hidden: true },
4648
+ { name: 'paymentDate', type: 'date', alias: 'Payment Date', required: true },
4649
+ { name: 'method', type: 'select', alias: 'Payment Method', required: true, defaultValue: 1, loadAction: { url: 'invoicepayments/list/methods' } }, // Changed: Default to 1 (Bank)
4650
+ { name: 'totalAmount', type: 'label', alias: 'Total Amount', readonly: true },
4651
+ { name: 'amount', type: 'money', alias: 'Amount', required: true, span: true },
4652
+ { name: 'paymentReferenceTemp', type: 'text', alias: 'Reference', span: true }
4653
+ ],
4654
+ };
4655
+ // Added: Sale payment button (matches invoice payment button pattern)
4656
+ this.salePaymentCreateButton = {
4657
+ name: 'edit', // Changed: Use standard 'edit' button name for framework recognition
4658
+ action: { url: 'sales?action=record-payment', method: 'post', successMessage: 'Payment recorded successfully' }
4659
+ };
4660
+ // Added: Sale payment details dialog config
4661
+ this.salePaymentDetailsConfig = {
4662
+ formConfig: this.salePaymentFormConfig,
4663
+ heroField: 'saleID',
4664
+ mode: 'edit',
4665
+ buttons: [
4666
+ this.salePaymentCreateButton // Changed: Use separate button definition
4667
+ ]
4668
+ };
4669
+ // Added: Sale payment record button
4670
+ this.saleRecordPaymentButton = {
4671
+ name: 'record-payment', display: 'Record Payment', inDialog: true, dialog: true, icon: { name: 'payment', color: 'blue' },
4672
+ detailsConfig: this.salePaymentDetailsConfig,
4673
+ visible: x => x.timing === 1 && x.paymentStatus !== 1
4674
+ };
4675
+ // Added: Sale payments table for payment history (from linked invoice)
4676
+ this.salePaymentsTableConfig = {
4677
+ tabTitle: 'Payment History',
4678
+ showFilter: false,
4679
+ minColumns: ['paymentDate', 'amount', 'methodName'],
4680
+ columns: [
4681
+ { name: 'paymentDate', type: 'date', alias: 'Date' },
4682
+ { name: 'amount', type: 'money', alias: 'Amount' },
4683
+ { name: 'methodName', type: 'text', alias: 'Method' },
4684
+ { name: 'reference', type: 'text', alias: 'Reference' }
4685
+ ],
4686
+ loadAction: { url: 'invoicepayments/x/x' }, loadCriteria: 'invoice', loadIDField: 'invoiceID',
4687
+ hideTabCondition: (x) => x.timing !== 1 // Changed: Only show for deferred payments (credit sales)
4688
+ };
4402
4689
  // Details dialog config for sale with complete button (only visible if not yet paid)
4403
4690
  this.saleDetailsConfig = {
4404
4691
  formConfig: this.saleFormConfig,
4405
- tableConfigs: [this.saleItemsTableConfig],
4692
+ tableConfigs: [this.saleItemsTableConfig, this.salePaymentsTableConfig], // Added: Include payment history table
4406
4693
  heroField: 'saleID',
4407
4694
  stepConfig: this.saleStepConfig, // Changed: Added step config for payment status tracking
4408
4695
  buttons: [
4409
4696
  { name: 'complete', display: 'Complete Sale', color: 'primary', inDialog: true,
4410
4697
  action: { url: 'sales?action=complete', method: 'post' },
4411
4698
  confirm: { message: 'Complete this sale? This will reduce inventory and create accounting entries.' },
4412
- visible: x => x.paymentStatus !== 1
4413
- }
4699
+ visible: x => x.timing === 0 && x.paymentStatus !== 1 // Changed: Only for immediate sales that aren't paid (deferred sales are auto-completed)
4700
+ },
4701
+ this.saleRecordPaymentButton // Added: Record payment button for credit sales
4414
4702
  ]
4415
4703
  };
4416
4704
  this.saleViewButton = { name: 'view', dialog: true, detailsConfig: this.saleDetailsConfig };
4417
4705
  this.saleCreateButton = {
4418
- name: 'create',
4419
- display: 'Quick Sale',
4420
- dialog: true,
4421
- action: { url: 'sales?action=create', method: 'post' },
4706
+ name: 'create', display: 'Quick Sale', dialog: true, action: { url: 'sales?action=create', method: 'post' },
4422
4707
  };
4423
4708
  this.saleVoidButton = { name: 'void', display: 'Void', icon: { name: 'undo', color: 'red' },
4424
4709
  confirm: { message: 'Caution: Voiding this sale will reverse all inventory transactions and void accounting entries. This action cannot be undone.' },
@@ -4433,7 +4718,7 @@ class InventoryService {
4433
4718
  { name: 'saleDate', type: 'date', alias: 'Date' },
4434
4719
  { name: 'displayCustomerName', type: 'text', alias: 'Customer' },
4435
4720
  { name: 'productsDisplay', type: 'text', alias: 'Products' }, // Added: Display product names or count
4436
- { name: 'paymentMethodName', type: 'text', alias: 'Payment' },
4721
+ { name: 'timingName', type: 'text', alias: 'Payment' },
4437
4722
  { name: 'paymentStatus', type: 'icon', alias: 'Status', detailsConfig: this.inventoryReceiptDetailsConfig, // Changed: Use status instead of statusName
4438
4723
  icons: [
4439
4724
  { name: 'article', color: '#9E9E9E', condition: (x) => x.paymentStatus === 0, tip: 'Unpaid' }, // Changed: Use status enum
@@ -4444,7 +4729,10 @@ class InventoryService {
4444
4729
  { name: 'totalAmount', type: 'money', alias: 'Total' }
4445
4730
  ],
4446
4731
  buttons: [
4447
- this.saleViewButton, this.saleCreateButton, this.saleVoidButton
4732
+ this.saleViewButton,
4733
+ this.saleRecordPaymentButton,
4734
+ this.saleCreateButton,
4735
+ this.saleVoidButton
4448
4736
  ],
4449
4737
  loadAction: { url: 'sales/all/x' },
4450
4738
  formConfig: this.saleFormConfig
@@ -4757,31 +5045,45 @@ class TabService {
4757
5045
  this.buttonService = buttonService;
4758
5046
  this.tableConfigService = tableConfigService;
4759
5047
  }
5048
+ // Replace {propertyName} placeholders in URL with values from parentData
5049
+ transformCountUrl(url, parentData) {
5050
+ if (!parentData || !url || !url.includes('{'))
5051
+ return url; // Changed: Only process if URL contains placeholders
5052
+ return url.replace(/\{(\w+)\}/g, (match, propName) => {
5053
+ const value = parentData[propName];
5054
+ return value !== undefined && value !== null ? String(value) : match;
5055
+ });
5056
+ }
4760
5057
  // Initialize tab counts and reload subjects for all tabs
4761
- initializeTabs(tableConfigs) {
5058
+ initializeTabs(tableConfigs, parentData) {
4762
5059
  const tabCounts = {};
4763
5060
  const tableReloads = {};
4764
5061
  tableConfigs.forEach((config, index) => {
4765
5062
  if (config.countAction) {
4766
- this.loadTabCount(index, config.countAction, tabCounts);
5063
+ this.loadTabCount(index, config.countAction, tabCounts, parentData); // Changed: Pass parentData for URL placeholder replacement
4767
5064
  }
4768
5065
  tableReloads[index] = new Subject();
4769
5066
  });
4770
5067
  return { tabCounts, tableReloads };
4771
5068
  }
4772
5069
  // Load count for a specific tab
4773
- loadTabCount(tabIndex, countAction, tabCounts) {
4774
- this.dataService.CallApi(countAction).subscribe((apiResponse) => {
5070
+ loadTabCount(tabIndex, countAction, tabCounts, parentData // Changed: Accept parentData for URL placeholder replacement
5071
+ ) {
5072
+ // Changed: Transform URL by replacing {propertyName} placeholders with parentData values
5073
+ const transformedUrl = this.transformCountUrl(countAction.url, parentData);
5074
+ const transformedAction = { ...countAction, url: transformedUrl };
5075
+ this.dataService.CallApi(transformedAction).subscribe((apiResponse) => {
4775
5076
  if (apiResponse.success) {
4776
5077
  tabCounts[tabIndex] = apiResponse.data;
4777
5078
  }
4778
5079
  });
4779
5080
  }
4780
5081
  // Refresh count for a specific tab
4781
- refreshTabCount(index, tableConfigs, tabCounts) {
5082
+ refreshTabCount(index, tableConfigs, tabCounts, parentData // Changed: Accept parentData for URL placeholder replacement
5083
+ ) {
4782
5084
  const config = tableConfigs[index];
4783
5085
  if (config?.countAction) {
4784
- this.loadTabCount(index, config.countAction, tabCounts);
5086
+ this.loadTabCount(index, config.countAction, tabCounts, parentData); // Changed: Pass parentData
4785
5087
  }
4786
5088
  }
4787
5089
  // Handle tab change - mark tab as loaded for lazy loading
@@ -5975,6 +6277,7 @@ class SelectCommonComponent {
5975
6277
  this.subscription = this.field.optionsSubject.subscribe(newOptions => {
5976
6278
  if (newOptions) {
5977
6279
  this.options = newOptions;
6280
+ this.setDefaultValue(); // Changed: Apply default after options update via subscription
5978
6281
  this.updateSelectedOptionHint();
5979
6282
  }
5980
6283
  });
@@ -5991,15 +6294,54 @@ class SelectCommonComponent {
5991
6294
  this.updateSelectedOptionHint();
5992
6295
  }
5993
6296
  setDefaultValue() {
5994
- if (this.defaultFirstValue && this.options && this.options.length > 0 &&
5995
- (this.value == null || this.value == undefined || (typeof (this.value) == 'string' && this.value == '') || (typeof (this.value) == 'number' && this.value == 0))) {
6297
+ // Guard: Need options to set any default
6298
+ if (!this.options || this.options.length === 0) {
6299
+ return;
6300
+ }
6301
+ // Guard: Check if value is already meaningfully set
6302
+ const hasValue = this.value != null &&
6303
+ this.value !== undefined &&
6304
+ !(typeof this.value === 'string' && this.value === '') &&
6305
+ !(typeof this.value === 'number' && this.value === 0 && !this.nullable);
6306
+ if (hasValue) {
6307
+ // Value already set - verify it exists in options
6308
+ const valueExists = this.options.some(opt => this.compareValues(opt[this.optionValue], this.value));
6309
+ if (valueExists) {
6310
+ return; // Valid value already selected
6311
+ }
6312
+ }
6313
+ // Priority 1: Use field.defaultValue if configured
6314
+ const defaultVal = this.field?.defaultValue;
6315
+ if (defaultVal != null && defaultVal !== undefined) {
6316
+ const matchingOption = this.options.find(opt => this.compareValues(opt[this.optionValue], defaultVal));
6317
+ if (matchingOption) {
6318
+ this.value = matchingOption[this.optionValue];
6319
+ this.changed();
6320
+ return;
6321
+ }
6322
+ }
6323
+ // Priority 2: Use defaultFirstValue if configured
6324
+ if (this.defaultFirstValue) {
5996
6325
  this.value = this.options[0][this.optionValue];
5997
6326
  this.changed();
5998
6327
  }
5999
6328
  }
6329
+ // Changed: Added helper method for type-coerced value comparison (handles number vs string)
6330
+ compareValues(optionValue, targetValue) {
6331
+ if (optionValue === targetValue)
6332
+ return true;
6333
+ if (optionValue == null || targetValue == null)
6334
+ return false;
6335
+ return String(optionValue) === String(targetValue);
6336
+ }
6000
6337
  changed() {
6001
6338
  this.valueChange.emit(this.value);
6002
6339
  this.updateSelectedOptionHint();
6340
+ // Added: Call onSelectChange callback if provided
6341
+ if (this.field?.onSelectChange && this.data) {
6342
+ const selectedOption = this.options?.find(opt => opt[this.optionValue] === this.value);
6343
+ this.field.onSelectChange(this.value, this.data, selectedOption);
6344
+ }
6003
6345
  }
6004
6346
  updateSelectedOptionHint() {
6005
6347
  if (!this.options || this.options.length === 0 || this.value == null || this.value == undefined) {
@@ -6038,7 +6380,18 @@ class SelectCommonComponent {
6038
6380
  this.getData(refreshAction);
6039
6381
  }
6040
6382
  transformLoadUrl(action) {
6041
- if (!action?.url || !this.loadIDField || !this.data)
6383
+ if (!action?.url || !this.data)
6384
+ return action;
6385
+ // Changed: Check if URL contains {propertyName} placeholders first
6386
+ if (action.url.includes('{')) {
6387
+ const transformedUrl = action.url.replace(/\{(\w+)\}/g, (match, propName) => {
6388
+ const value = this.data?.[propName];
6389
+ return value !== undefined && value !== null ? String(value) : match;
6390
+ });
6391
+ return { ...action, url: transformedUrl };
6392
+ }
6393
+ // Changed: Fallback to existing loadIDField pattern for backward compatibility
6394
+ if (!this.loadIDField)
6042
6395
  return action;
6043
6396
  const idValue = this.data[this.loadIDField];
6044
6397
  if (!idValue)
@@ -6956,7 +7309,18 @@ class ViewerComponent {
6956
7309
  // fileList: string[];
6957
7310
  loadData() {
6958
7311
  console.log("Calling files");
6959
- let url = this.fileAction.url.replace("/x", '/' + this.folderName);
7312
+ let url = this.fileAction.url;
7313
+ // Changed: Support generic {propertyName} placeholders first
7314
+ if (url.includes('{')) {
7315
+ url = url.replace(/\{(\w+)\}/g, (match, propName) => {
7316
+ const value = { folderName: this.folderName }[propName];
7317
+ return value !== undefined && value !== null ? String(value) : match;
7318
+ });
7319
+ }
7320
+ else {
7321
+ // Fallback: Legacy /x replacement for backward compatibility
7322
+ url = url.replace("/x", '/' + this.folderName);
7323
+ }
6960
7324
  this.dataService.CallApi({ url: url }, "").subscribe((apiResponse) => {
6961
7325
  this.fileNames = apiResponse.data;
6962
7326
  // console.log(this.fileNames)
@@ -7532,12 +7896,22 @@ class NotesComponent {
7532
7896
  return;
7533
7897
  }
7534
7898
  // Otherwise, load from the URL if configured
7535
- if (this.loadAction && this.data && this.loadIDField) {
7536
- const idValue = this.data[this.loadIDField];
7537
- if (!idValue) {
7538
- return;
7899
+ if (this.loadAction && this.data) {
7900
+ let url = this.loadAction.url;
7901
+ // Changed: Support generic {propertyName} placeholders first
7902
+ if (url.includes('{')) {
7903
+ url = url.replace(/\{(\w+)\}/g, (match, propName) => {
7904
+ const value = this.data?.[propName];
7905
+ return value !== undefined && value !== null ? String(value) : match;
7906
+ });
7907
+ }
7908
+ else if (this.loadIDField) {
7909
+ // Fallback: Use loadIDField for backward compatibility
7910
+ const idValue = this.data[this.loadIDField];
7911
+ if (!idValue)
7912
+ return;
7913
+ url = url.replace('/x', '/' + idValue);
7539
7914
  }
7540
- let url = this.loadAction.url.replace('{x}', idValue);
7541
7915
  this.dataService.CallApi({ ...this.loadAction, url }).subscribe((apiResponse) => {
7542
7916
  if (apiResponse.success && apiResponse.data) {
7543
7917
  this.notes = apiResponse.data;
@@ -9882,7 +10256,7 @@ class TabsLiteComponent {
9882
10256
  }
9883
10257
  ngOnInit() {
9884
10258
  // Initialize tabs with counts and reload subjects using service
9885
- const initialized = this.tabService.initializeTabs(this.tableConfigs);
10259
+ const initialized = this.tabService.initializeTabs(this.tableConfigs, this.parentDetails); // Changed: Pass parentDetails for URL placeholder replacement
9886
10260
  this.tabCounts = initialized.tabCounts;
9887
10261
  this.tableReloads = initialized.tableReloads;
9888
10262
  }
@@ -9909,7 +10283,7 @@ class TabsLiteComponent {
9909
10283
  }
9910
10284
  // Refresh count for specific tab
9911
10285
  refreshTabCount(index) {
9912
- this.tabService.refreshTabCount(index, this.tableConfigs, this.tabCounts);
10286
+ this.tabService.refreshTabCount(index, this.tableConfigs, this.tabCounts, this.parentDetails); // Changed: Pass parentDetails for URL placeholder replacement
9913
10287
  }
9914
10288
  // Handle action success from table
9915
10289
  onTableActionSuccess(tabIndex, event) {
@@ -11113,7 +11487,7 @@ class TabsInternalComponent {
11113
11487
  }
11114
11488
  ngOnInit() {
11115
11489
  // Initialize tabs with counts and reload subjects using service
11116
- const initialized = this.tabService.initializeTabs(this.tableConfigs);
11490
+ const initialized = this.tabService.initializeTabs(this.tableConfigs, this.parentDetails); // Changed: Pass parentDetails for URL placeholder replacement
11117
11491
  this.tabCounts = initialized.tabCounts;
11118
11492
  this.tableReloads = initialized.tableReloads;
11119
11493
  }
@@ -11140,7 +11514,7 @@ class TabsInternalComponent {
11140
11514
  }
11141
11515
  // Refresh count for specific tab
11142
11516
  refreshTabCount(index) {
11143
- this.tabService.refreshTabCount(index, this.tableConfigs, this.tabCounts);
11517
+ this.tabService.refreshTabCount(index, this.tableConfigs, this.tabCounts, this.parentDetails); // Changed: Pass parentDetails for URL placeholder replacement
11144
11518
  }
11145
11519
  // Handle action success from table
11146
11520
  onTableActionSuccess(tabIndex, event) {
@@ -13536,7 +13910,7 @@ class TabsComponent {
13536
13910
  }
13537
13911
  ngOnInit() {
13538
13912
  // Initialize tabs with counts and reload subjects using service
13539
- const initialized = this.tabService.initializeTabs(this.tableConfigs);
13913
+ const initialized = this.tabService.initializeTabs(this.tableConfigs, this.parentDetails); // Changed: Pass parentDetails for URL placeholder replacement
13540
13914
  this.tabCounts = initialized.tabCounts;
13541
13915
  this.tableReloads = initialized.tableReloads;
13542
13916
  }
@@ -13563,7 +13937,7 @@ class TabsComponent {
13563
13937
  }
13564
13938
  // Refresh count for specific tab
13565
13939
  refreshTabCount(index) {
13566
- this.tabService.refreshTabCount(index, this.tableConfigs, this.tabCounts);
13940
+ this.tabService.refreshTabCount(index, this.tableConfigs, this.tabCounts, this.parentDetails); // Changed: Pass parentDetails for URL placeholder replacement
13567
13941
  }
13568
13942
  // Changed: Handle action success from table (matching tabs-internal)
13569
13943
  onTableActionSuccess(tabIndex, event) {
@@ -14827,11 +15201,11 @@ class RolesComponent {
14827
15201
  });
14828
15202
  }
14829
15203
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: RolesComponent, deps: [{ token: HttpService }, { token: i1$2.Router }, { token: AuthService }, { token: DataServiceLib }, { token: DialogService }, { token: i5.MatDialog }, { token: MessageService }], target: i0.ɵɵFactoryTarget.Component }); }
14830
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: RolesComponent, isStandalone: false, selector: "spa-roles", ngImport: i0, template: "<h4> Roles </h4>\r\n<hr />\r\n\r\n<div class=\"container-fluid mb-5\">\r\n\r\n <div class=\"d-flex justify-content-between mb-2\">\r\n\r\n <div >\r\n <button id=\"btnNewRole\" mat-raised-button color=\"primary\" (click)=\"addRole()\">New Role</button>\r\n </div>\r\n\r\n <div class=\"d-flex justify-content-end\">\r\n <button id=\"btnRefresh\" mat-icon-button color=\"primary\" (click)=\"refresh()\" matTooltip=\"refresh data\" matTooltipPosition=\"right\"><mat-icon >refresh</mat-icon></button>\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <div class=\"row mt-2 mb-1\" *ngFor=\"let role of roles\">\r\n\r\n <mat-card class=\"mat-elevation-z8\" style=\"width:100%\">\r\n\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n\r\n <label style=\"font-size: 16px;\">{{role.roleName}}</label>\r\n\r\n <button mat-icon-button color=\"primary\" matTooltip=\"Rename Role\" (click)=\"renameRole(role)\">\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <hr style=\"margin-top: 0px;\">\r\n\r\n <div class=\"tin-row\" style=\" font-size:12px;\">\r\n\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capItem of appConfig.capItems\">\r\n\r\n <!-- Main item-->\r\n <mat-checkbox *ngIf=\"capItem.isBool || capItem.capSubItems\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capItem.name]\" (ngModelChange)=\"onCapItemChange(capItem, $event, role)\">\r\n {{capItem.display}}\r\n <span *ngIf=\"!role[capItem.name] && hasSubItemsAccess(capItem, role)\" class=\"asterisk\" style=\"color: red;\">*</span>\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capItem.isBool && !capItem.capSubItems\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capItem.display\"\r\n [(value)]=\"role[capItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n\r\n <ng-container *ngIf=\"capItem.capSubItems && role[capItem.name]\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubItem of capItem.capSubItems\">\r\n\r\n\r\n <!-- Sub Item -->\r\n <mat-checkbox *ngIf=\"capSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubItem.name]\">\r\n {{capSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubItem.display\"\r\n [(value)]=\"role[capSubItem.name]\"\r\n width=\"150px\"\r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n <ng-container *ngIf=\"capSubItem.capSubItems\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubSubItem of capSubItem.capSubItems\">\r\n\r\n <!-- Sub Sub Items -->\r\n <mat-checkbox *ngIf=\"capSubSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubSubItem.name]\">\r\n {{capSubSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubSubItem.display\"\r\n [(value)]=\"role[capSubSubItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <mat-card-actions>\r\n\r\n <button mat-raised-button color=\"primary\" (click)=\"updateRole(role)\" style=\"margin-right:10px;\">\r\n <mat-icon>done_all</mat-icon>\r\n Update\r\n </button>\r\n\r\n <button mat-raised-button (click)=\"deleteRole(role)\" style=\"margin-right:10px\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n </div>\r\n\r\n\r\n</div>\r\n\r\n", styles: [""], dependencies: [{ kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i5$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6$2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$2.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }] }); }
15204
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: RolesComponent, isStandalone: false, selector: "spa-roles", ngImport: i0, template: "<h4> Roles </h4>\r\n<hr />\r\n\r\n<div class=\"container-fluid mb-5\">\r\n\r\n <div class=\"d-flex justify-content-between mb-2\">\r\n\r\n <div >\r\n <button id=\"btnNewRole\" mat-raised-button color=\"primary\" (click)=\"addRole()\">New Role</button>\r\n </div>\r\n\r\n <div class=\"d-flex justify-content-end\">\r\n <button id=\"btnRefresh\" mat-icon-button color=\"primary\" (click)=\"refresh()\" matTooltip=\"refresh data\" matTooltipPosition=\"right\"><mat-icon >refresh</mat-icon></button>\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <div class=\"row mt-2 mb-1\" *ngFor=\"let role of roles\">\r\n\r\n <mat-card class=\"mat-elevation-z8\" style=\"width:100%\">\r\n\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n\r\n <label style=\"font-size: 16px;\">{{role.roleName}}</label>\r\n\r\n <button mat-icon-button color=\"primary\" matTooltip=\"Rename Role\" (click)=\"renameRole(role)\">\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <hr style=\"margin-top: 0px;\">\r\n\r\n <div class=\"tin-row\" style=\" font-size:12px;\">\r\n\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capItem of appConfig.capItems\">\r\n\r\n <!-- Main item-->\r\n <mat-checkbox *ngIf=\"capItem.isBool || capItem.capSubItems\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capItem.name]\" (ngModelChange)=\"onCapItemChange(capItem, $event, role)\">\r\n {{capItem.display}}\r\n <span *ngIf=\"!role[capItem.name] && hasSubItemsAccess(capItem, role)\" class=\"asterisk\" style=\"color: red;\">*</span>\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capItem.isBool && !capItem.capSubItems\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capItem.display\"\r\n [(value)]=\"role[capItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n\r\n <ng-container *ngIf=\"capItem.capSubItems && role[capItem.name]\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubItem of capItem.capSubItems\">\r\n\r\n\r\n <!-- Sub Item -->\r\n <mat-checkbox *ngIf=\"capSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubItem.name]\">\r\n {{capSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubItem.display\"\r\n [(value)]=\"role[capSubItem.name]\"\r\n width=\"150px\"\r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n <ng-container *ngIf=\"capSubItem.capSubItems\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubSubItem of capSubItem.capSubItems\">\r\n\r\n <!-- Sub Sub Items -->\r\n <mat-checkbox *ngIf=\"capSubSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubSubItem.name]\">\r\n {{capSubSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubSubItem.display\"\r\n [(value)]=\"role[capSubSubItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <mat-card-actions>\r\n\r\n <button mat-raised-button color=\"primary\" (click)=\"updateRole(role)\" style=\"margin-right:10px;\">\r\n <mat-icon>done_all</mat-icon>\r\n Update\r\n </button>\r\n\r\n <button mat-raised-button (click)=\"deleteRole(role)\" style=\"margin-right:10px\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n </div>\r\n\r\n <hr style=\"margin-top: 50px;\" />\r\n\r\n\r\n</div>\r\n\r\n", styles: [""], dependencies: [{ kind: "directive", type: i2$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i5$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i6$2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i6$2.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i7.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }] }); }
14831
15205
  }
14832
15206
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: RolesComponent, decorators: [{
14833
15207
  type: Component,
14834
- args: [{ selector: "spa-roles", standalone: false, template: "<h4> Roles </h4>\r\n<hr />\r\n\r\n<div class=\"container-fluid mb-5\">\r\n\r\n <div class=\"d-flex justify-content-between mb-2\">\r\n\r\n <div >\r\n <button id=\"btnNewRole\" mat-raised-button color=\"primary\" (click)=\"addRole()\">New Role</button>\r\n </div>\r\n\r\n <div class=\"d-flex justify-content-end\">\r\n <button id=\"btnRefresh\" mat-icon-button color=\"primary\" (click)=\"refresh()\" matTooltip=\"refresh data\" matTooltipPosition=\"right\"><mat-icon >refresh</mat-icon></button>\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <div class=\"row mt-2 mb-1\" *ngFor=\"let role of roles\">\r\n\r\n <mat-card class=\"mat-elevation-z8\" style=\"width:100%\">\r\n\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n\r\n <label style=\"font-size: 16px;\">{{role.roleName}}</label>\r\n\r\n <button mat-icon-button color=\"primary\" matTooltip=\"Rename Role\" (click)=\"renameRole(role)\">\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <hr style=\"margin-top: 0px;\">\r\n\r\n <div class=\"tin-row\" style=\" font-size:12px;\">\r\n\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capItem of appConfig.capItems\">\r\n\r\n <!-- Main item-->\r\n <mat-checkbox *ngIf=\"capItem.isBool || capItem.capSubItems\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capItem.name]\" (ngModelChange)=\"onCapItemChange(capItem, $event, role)\">\r\n {{capItem.display}}\r\n <span *ngIf=\"!role[capItem.name] && hasSubItemsAccess(capItem, role)\" class=\"asterisk\" style=\"color: red;\">*</span>\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capItem.isBool && !capItem.capSubItems\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capItem.display\"\r\n [(value)]=\"role[capItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n\r\n <ng-container *ngIf=\"capItem.capSubItems && role[capItem.name]\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubItem of capItem.capSubItems\">\r\n\r\n\r\n <!-- Sub Item -->\r\n <mat-checkbox *ngIf=\"capSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubItem.name]\">\r\n {{capSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubItem.display\"\r\n [(value)]=\"role[capSubItem.name]\"\r\n width=\"150px\"\r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n <ng-container *ngIf=\"capSubItem.capSubItems\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubSubItem of capSubItem.capSubItems\">\r\n\r\n <!-- Sub Sub Items -->\r\n <mat-checkbox *ngIf=\"capSubSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubSubItem.name]\">\r\n {{capSubSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubSubItem.display\"\r\n [(value)]=\"role[capSubSubItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <mat-card-actions>\r\n\r\n <button mat-raised-button color=\"primary\" (click)=\"updateRole(role)\" style=\"margin-right:10px;\">\r\n <mat-icon>done_all</mat-icon>\r\n Update\r\n </button>\r\n\r\n <button mat-raised-button (click)=\"deleteRole(role)\" style=\"margin-right:10px\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n </div>\r\n\r\n\r\n</div>\r\n\r\n" }]
15208
+ args: [{ selector: "spa-roles", standalone: false, template: "<h4> Roles </h4>\r\n<hr />\r\n\r\n<div class=\"container-fluid mb-5\">\r\n\r\n <div class=\"d-flex justify-content-between mb-2\">\r\n\r\n <div >\r\n <button id=\"btnNewRole\" mat-raised-button color=\"primary\" (click)=\"addRole()\">New Role</button>\r\n </div>\r\n\r\n <div class=\"d-flex justify-content-end\">\r\n <button id=\"btnRefresh\" mat-icon-button color=\"primary\" (click)=\"refresh()\" matTooltip=\"refresh data\" matTooltipPosition=\"right\"><mat-icon >refresh</mat-icon></button>\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <div class=\"row mt-2 mb-1\" *ngFor=\"let role of roles\">\r\n\r\n <mat-card class=\"mat-elevation-z8\" style=\"width:100%\">\r\n\r\n <div class=\"d-flex justify-content-between align-items-center\">\r\n\r\n <label style=\"font-size: 16px;\">{{role.roleName}}</label>\r\n\r\n <button mat-icon-button color=\"primary\" matTooltip=\"Rename Role\" (click)=\"renameRole(role)\">\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <hr style=\"margin-top: 0px;\">\r\n\r\n <div class=\"tin-row\" style=\" font-size:12px;\">\r\n\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capItem of appConfig.capItems\">\r\n\r\n <!-- Main item-->\r\n <mat-checkbox *ngIf=\"capItem.isBool || capItem.capSubItems\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capItem.name]\" (ngModelChange)=\"onCapItemChange(capItem, $event, role)\">\r\n {{capItem.display}}\r\n <span *ngIf=\"!role[capItem.name] && hasSubItemsAccess(capItem, role)\" class=\"asterisk\" style=\"color: red;\">*</span>\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capItem.isBool && !capItem.capSubItems\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capItem.display\"\r\n [(value)]=\"role[capItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n\r\n <ng-container *ngIf=\"capItem.capSubItems && role[capItem.name]\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubItem of capItem.capSubItems\">\r\n\r\n\r\n <!-- Sub Item -->\r\n <mat-checkbox *ngIf=\"capSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubItem.name]\">\r\n {{capSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubItem.display\"\r\n [(value)]=\"role[capSubItem.name]\"\r\n width=\"150px\"\r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n <ng-container *ngIf=\"capSubItem.capSubItems\">\r\n\r\n <div class=\"tin-row\" *ngFor=\"let capSubSubItem of capSubItem.capSubItems\">\r\n\r\n <!-- Sub Sub Items -->\r\n <mat-checkbox *ngIf=\"capSubSubItem.isBool\"\r\n color=\"primary\" style=\"min-width: 100px;\" [(ngModel)]=\"role[capSubSubItem.name]\">\r\n {{capSubSubItem.display}}\r\n </mat-checkbox>\r\n\r\n <spa-select\r\n *ngIf=\"!capSubSubItem.isBool\"\r\n [options]=\"roleAccessOptions\"\r\n optionDisplay=\"name\"\r\n optionValue=\"value\"\r\n [display]=\"capSubSubItem.display\"\r\n [(value)]=\"role[capSubSubItem.name]\"\r\n width=\"150px\" \r\n style=\"font-size: 12px;\">\r\n </spa-select>\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n\r\n\r\n </div>\r\n\r\n </ng-container>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n\r\n <mat-card-actions>\r\n\r\n <button mat-raised-button color=\"primary\" (click)=\"updateRole(role)\" style=\"margin-right:10px;\">\r\n <mat-icon>done_all</mat-icon>\r\n Update\r\n </button>\r\n\r\n <button mat-raised-button (click)=\"deleteRole(role)\" style=\"margin-right:10px\">\r\n <mat-icon>delete</mat-icon>\r\n Delete\r\n </button>\r\n\r\n </mat-card-actions>\r\n\r\n </mat-card>\r\n\r\n </div>\r\n\r\n <hr style=\"margin-top: 50px;\" />\r\n\r\n\r\n</div>\r\n\r\n" }]
14835
15209
  }], ctorParameters: () => [{ type: HttpService }, { type: i1$2.Router }, { type: AuthService }, { type: DataServiceLib }, { type: DialogService }, { type: i5.MatDialog }, { type: MessageService }] });
14836
15210
 
14837
15211
  class CreateAccountComponent {
@@ -16039,6 +16413,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
16039
16413
  }]
16040
16414
  }] });
16041
16415
 
16416
+ // ServiceItems component for managing service items
16417
+ class ServiceItemsComponent {
16418
+ constructor() {
16419
+ this.inventoryService = inject(InventoryService);
16420
+ this.pageConfig = {
16421
+ title: 'Services',
16422
+ tableConfig: this.inventoryService.serviceItemsTableConfig
16423
+ };
16424
+ }
16425
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ServiceItemsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
16426
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: ServiceItemsComponent, isStandalone: false, selector: "spa-service-items", ngImport: i0, template: '<spa-page [config]="pageConfig"></spa-page>', isInline: true, dependencies: [{ kind: "component", type: PageComponent, selector: "spa-page", inputs: ["config"], outputs: ["searchModeActivated", "searchModeDeactivated", "refreshClick", "actionClick", "actionResponse", "inputChange", "createClick", "searchClick", "dataLoad", "titleActionChange"] }] }); }
16427
+ }
16428
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: ServiceItemsComponent, decorators: [{
16429
+ type: Component,
16430
+ args: [{
16431
+ selector: 'spa-service-items',
16432
+ standalone: false,
16433
+ template: '<spa-page [config]="pageConfig"></spa-page>'
16434
+ }]
16435
+ }] });
16436
+
16042
16437
  class GptCachesComponent {
16043
16438
  constructor() {
16044
16439
  this.dataService = inject(DataServiceLib);
@@ -16099,6 +16494,7 @@ const routes$1 = [
16099
16494
  { path: "loans", component: LoansComponent },
16100
16495
  { path: "loans-payments", component: LoanPaymentsComponent },
16101
16496
  { path: "inventory-products", component: ProductsComponent },
16497
+ { path: "inventory-service-items", component: ServiceItemsComponent }, // Added: Service items route
16102
16498
  { path: "inventory-items", component: InventoryItemsComponent },
16103
16499
  { path: "purchase-orders", component: PurchaseOrdersComponent },
16104
16500
  { path: "inventory-receipts", component: InventoryReceiptsComponent },
@@ -16155,7 +16551,8 @@ class AdminModule {
16155
16551
  InventoryStockComponent,
16156
16552
  ProductionRecipesComponent,
16157
16553
  ProductionOrdersComponent,
16158
- BundleProductsComponent // Added: Bundle products component
16554
+ BundleProductsComponent, // Added: Bundle products component
16555
+ ServiceItemsComponent // Added: Service items component
16159
16556
  ], imports: [CommonModule,
16160
16557
  AdminRoutingModule,
16161
16558
  SpaAdminModule] }); }
@@ -16191,7 +16588,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
16191
16588
  InventoryStockComponent,
16192
16589
  ProductionRecipesComponent,
16193
16590
  ProductionOrdersComponent,
16194
- BundleProductsComponent // Added: Bundle products component
16591
+ BundleProductsComponent, // Added: Bundle products component
16592
+ ServiceItemsComponent // Added: Service items component
16195
16593
  ],
16196
16594
  imports: [
16197
16595
  CommonModule,
@@ -16248,5 +16646,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
16248
16646
  * Generated bundle index. Do not edit.
16249
16647
  */
16250
16648
 
16251
- export { Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, InvoicesComponent as AccountingInvoicesComponent, OutstandingInvoicesComponent as AccountingOutstandingInvoicesComponent, AccountingService, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AlertComponent, AlertConfig, AlertMessage, ApiResponse, AppConfig, AppModelsComponent, AttachComponent, AuthService, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FilterComponent, FormComponent, FormConfig, GeneralService, GradesComponent, GroupsComponent, HtmlComponent, HttpService, IndexModule, InventoryReceiptStatus, InventoryService, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NumberComponent, OnboardingComponent, OptionComponent, PageComponent, PageConfig, PlansComponent, PositionsComponent, Profile, ProfileComponent, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SpaAdminModule, SpaIndexModule, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SuppliersComponent, TabService, TableComponent, TableConfig, TabsComponent, TabsInternalComponent, TabsLiteComponent, TasksComponent, TenantsComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, UnitOfMeasure, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, authGuard, dialogOptions, loginConfig, messageDialog, viewerDialog };
16649
+ export { Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, InvoicesComponent as AccountingInvoicesComponent, OutstandingInvoicesComponent as AccountingOutstandingInvoicesComponent, AccountingService, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AlertComponent, AlertConfig, AlertMessage, ApiResponse, AppConfig, AppModelsComponent, AttachComponent, AuthService, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FilterComponent, FormComponent, FormConfig, GeneralService, GradesComponent, GroupsComponent, HtmlComponent, HttpService, IndexModule, InventoryReceiptStatus, InventoryService, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NumberComponent, OnboardingComponent, OptionComponent, PageComponent, PageConfig, PlansComponent, PositionsComponent, Profile, ProfileComponent, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SpaAdminModule, SpaIndexModule, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SuppliersComponent, TabService, TableComponent, TableConfig, TabsComponent, TabsInternalComponent, TabsLiteComponent, TasksComponent, TenantsComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, authGuard, dialogOptions, loginConfig, messageDialog, viewerDialog };
16252
16650
  //# sourceMappingURL=tin-spa.mjs.map