mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 02:36:28 +00:00 
			
		
		
		
	Make sure components are edit-ready.
This commit is contained in:
		| @@ -37,6 +37,7 @@ use Illuminate\Http\JsonResponse; | ||||
| use Illuminate\Validation\ValidationException; | ||||
| use League\Fractal\Resource\Item; | ||||
| use Log; | ||||
| use Validator; | ||||
|  | ||||
| /** | ||||
|  * Class StoreController | ||||
|   | ||||
							
								
								
									
										166
									
								
								frontend/src/components/shared/transactions.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								frontend/src/components/shared/transactions.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | ||||
| /* | ||||
|  * transactions.js | ||||
|  * Copyright (c) 2021 james@firefly-iii.org | ||||
|  * | ||||
|  * This file is part of Firefly III (https://github.com/firefly-iii). | ||||
|  * | ||||
|  * This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| export function getDefaultErrors() { | ||||
|     return { | ||||
|         description: [], | ||||
|         amount: [], | ||||
|         source: [], | ||||
|         destination: [], | ||||
|         currency: [], | ||||
|         foreign_currency: [], | ||||
|         foreign_amount: [], | ||||
|         date: [], | ||||
|         custom_dates: [], | ||||
|         budget: [], | ||||
|         category: [], | ||||
|         bill: [], | ||||
|         tags: [], | ||||
|         piggy_bank: [], | ||||
|         internal_reference: [], | ||||
|         external_url: [], | ||||
|         notes: [], | ||||
|         location: [] | ||||
|     }; | ||||
| } | ||||
|  | ||||
| export function getDefaultTransaction() { | ||||
|     return { | ||||
|         // basic | ||||
|         description: '', | ||||
|         transaction_journal_id: 0, | ||||
|         // accounts: | ||||
|         source_account_id: null, | ||||
|         source_account_name: null, | ||||
|         source_account_type: null, | ||||
|  | ||||
|         source_account_currency_id: null, | ||||
|         source_account_currency_code: null, | ||||
|         source_account_currency_symbol: null, | ||||
|  | ||||
|         destination_account_id: null, | ||||
|         destination_account_name: null, | ||||
|         destination_account_type: null, | ||||
|  | ||||
|         destination_account_currency_id: null, | ||||
|         destination_account_currency_code: null, | ||||
|         destination_account_currency_symbol: null, | ||||
|  | ||||
|         source_account: { | ||||
|             id: 0, | ||||
|             name: "", | ||||
|             name_with_balance: "", | ||||
|             type: "", | ||||
|             currency_id: 0, | ||||
|             currency_name: '', | ||||
|             currency_code: '', | ||||
|             currency_decimal_places: 2 | ||||
|         }, | ||||
|         destination_account: { | ||||
|             id: 0, | ||||
|             name: "", | ||||
|             type: "", | ||||
|             currency_id: 0, | ||||
|             currency_name: '', | ||||
|             currency_code: '', | ||||
|             currency_decimal_places: 2 | ||||
|         }, | ||||
|  | ||||
|         // amount: | ||||
|         amount: '', | ||||
|         currency_id: 0, | ||||
|         foreign_amount: '', | ||||
|         foreign_currency_id: 0, | ||||
|  | ||||
|         // meta data | ||||
|         category: null, | ||||
|         budget_id: 0, | ||||
|         bill_id: 0, | ||||
|         piggy_bank_id: 0, | ||||
|         tags: [], | ||||
|  | ||||
|         // optional date fields (6x): | ||||
|         interest_date: null, | ||||
|         book_date: null, | ||||
|         process_date: null, | ||||
|         due_date: null, | ||||
|         payment_date: null, | ||||
|         invoice_date: null, | ||||
|  | ||||
|         // optional other fields: | ||||
|         internal_reference: null, | ||||
|         external_url: null, | ||||
|         external_id: null, | ||||
|         notes: null, | ||||
|  | ||||
|         // transaction links: | ||||
|         links: [], | ||||
|         attachments: [], | ||||
|         // location: | ||||
|         zoom_level: null, | ||||
|         longitude: null, | ||||
|         latitude: null, | ||||
|  | ||||
|         // error handling | ||||
|         errors: {}, | ||||
|     } | ||||
| } | ||||
|  | ||||
| export function toW3CString(date) { | ||||
|     // https://gist.github.com/tristanlins/6585391 | ||||
|     let year = date.getFullYear(); | ||||
|     let month = date.getMonth(); | ||||
|     month++; | ||||
|     if (month < 10) { | ||||
|         month = '0' + month; | ||||
|     } | ||||
|     let day = date.getDate(); | ||||
|     if (day < 10) { | ||||
|         day = '0' + day; | ||||
|     } | ||||
|     let hours = date.getHours(); | ||||
|     if (hours < 10) { | ||||
|         hours = '0' + hours; | ||||
|     } | ||||
|     let minutes = date.getMinutes(); | ||||
|     if (minutes < 10) { | ||||
|         minutes = '0' + minutes; | ||||
|     } | ||||
|     let seconds = date.getSeconds(); | ||||
|     if (seconds < 10) { | ||||
|         seconds = '0' + seconds; | ||||
|     } | ||||
|     let offset = -date.getTimezoneOffset(); | ||||
|     let offsetHours = Math.abs(Math.floor(offset / 60)); | ||||
|     let offsetMinutes = Math.abs(offset) - offsetHours * 60; | ||||
|     if (offsetHours < 10) { | ||||
|         offsetHours = '0' + offsetHours; | ||||
|     } | ||||
|     if (offsetMinutes < 10) { | ||||
|         offsetMinutes = '0' + offsetMinutes; | ||||
|     } | ||||
|     let offsetSign = '+'; | ||||
|     if (offset < 0) { | ||||
|         offsetSign = '-'; | ||||
|     } | ||||
|     return year + '-' + month + '-' + day + | ||||
|            'T' + hours + ':' + minutes + ':' + seconds + | ||||
|            offsetSign + offsetHours + ':' + offsetMinutes; | ||||
| } | ||||
| @@ -20,6 +20,8 @@ | ||||
|  | ||||
| const lodashClonedeep = require('lodash.clonedeep'); | ||||
|  | ||||
| import {getDefaultTransaction, getDefaultErrors} from '../../../shared/transactions'; | ||||
|  | ||||
| // initial state | ||||
| const state = () => ({ | ||||
|         transactionType: 'any', | ||||
| @@ -33,105 +35,8 @@ const state = () => ({ | ||||
|             payment_date: false, | ||||
|             invoice_date: false, | ||||
|         }, | ||||
|         defaultErrors: { | ||||
|             description: [], | ||||
|             amount: [], | ||||
|             source: [], | ||||
|             destination: [], | ||||
|             currency: [], | ||||
|             foreign_currency: [], | ||||
|             foreign_amount: [], | ||||
|             date: [], | ||||
|             custom_dates: [], | ||||
|             budget: [], | ||||
|             category: [], | ||||
|             bill: [], | ||||
|             tags: [], | ||||
|             piggy_bank: [], | ||||
|             internal_reference: [], | ||||
|             external_url: [], | ||||
|             notes: [], | ||||
|             location: [] | ||||
|         }, | ||||
|         defaultTransaction: { | ||||
|             // basic | ||||
|             description: '', | ||||
|             transaction_journal_id: 0, | ||||
|             // accounts: | ||||
|             source_account_id: null, | ||||
|             source_account_name: null, | ||||
|             source_account_type: null, | ||||
|  | ||||
|             source_account_currency_id: null, | ||||
|             source_account_currency_code: null, | ||||
|             source_account_currency_symbol: null, | ||||
|  | ||||
|             destination_account_id: null, | ||||
|             destination_account_name: null, | ||||
|             destination_account_type: null, | ||||
|  | ||||
|             destination_account_currency_id: null, | ||||
|             destination_account_currency_code: null, | ||||
|             destination_account_currency_symbol: null, | ||||
|  | ||||
|             source_account: { | ||||
|                 id: 0, | ||||
|                 name: "", | ||||
|                 name_with_balance: "", | ||||
|                 type: "", | ||||
|                 currency_id: 0, | ||||
|                 currency_name: '', | ||||
|                 currency_code: '', | ||||
|                 currency_decimal_places: 2 | ||||
|             }, | ||||
|             destination_account: { | ||||
|                 id: 0, | ||||
|                 name: "", | ||||
|                 type: "", | ||||
|                 currency_id: 0, | ||||
|                 currency_name: '', | ||||
|                 currency_code: '', | ||||
|                 currency_decimal_places: 2 | ||||
|             }, | ||||
|  | ||||
|             // amount: | ||||
|             amount: '', | ||||
|             currency_id: 0, | ||||
|             foreign_amount: '', | ||||
|             foreign_currency_id: 0, | ||||
|  | ||||
|             // meta data | ||||
|             category: null, | ||||
|             budget_id: 0, | ||||
|             bill_id: 0, | ||||
|             piggy_bank_id: 0, | ||||
|             tags: [], | ||||
|  | ||||
|             // optional date fields (6x): | ||||
|             interest_date: null, | ||||
|             book_date: null, | ||||
|             process_date: null, | ||||
|             due_date: null, | ||||
|             payment_date: null, | ||||
|             invoice_date: null, | ||||
|  | ||||
|             // optional other fields: | ||||
|             internal_reference: null, | ||||
|             external_url: null, | ||||
|             external_id: null, | ||||
|             notes: null, | ||||
|  | ||||
|             // transaction links: | ||||
|             links: [], | ||||
|             attachments: [], | ||||
|             // location: | ||||
|             zoom_level: null, | ||||
|             longitude: null, | ||||
|             latitude: null, | ||||
|  | ||||
|             // error handling | ||||
|             errors: {}, | ||||
|         }, | ||||
|         defaultTransaction: getDefaultTransaction(), | ||||
|         defaultErrors: getDefaultErrors() | ||||
|     } | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -119,6 +119,7 @@ import Alert from '../partials/Alert'; | ||||
| import SplitPills from "./SplitPills"; | ||||
| import TransactionGroupTitle from "./TransactionGroupTitle"; | ||||
| import SplitForm from "./SplitForm"; | ||||
| import {toW3CString} from '../shared/transactions'; | ||||
|  | ||||
| const {mapState, mapGetters, mapActions, mapMutations} = createNamespacedHelpers('transactions/create') | ||||
|  | ||||
| @@ -130,10 +131,13 @@ export default { | ||||
|     SplitPills, | ||||
|     TransactionGroupTitle, | ||||
|   }, | ||||
|   /** | ||||
|    * Grab some stuff from the API, add the first transaction. | ||||
|    */ | ||||
|   created() { | ||||
|     this.storeAllowedOpposingTypes(); | ||||
|     this.storeAccountToTransaction(); | ||||
|     this.storeCustomFields(); | ||||
|     this.getAllowedOpposingTypes(); | ||||
|     this.getAccountToTransaction(); | ||||
|     this.getCustomFields(); | ||||
|     this.addTransaction(); | ||||
|   }, | ||||
|   data() { | ||||
| @@ -142,8 +146,8 @@ export default { | ||||
|       errorMessage: '', | ||||
|       successMessage: '', | ||||
|  | ||||
|       // custom fields, useful for components: | ||||
|       customFields: [], | ||||
|       // custom fields to show, useful for components: | ||||
|       customFields: {}, | ||||
|  | ||||
|       // states for the form (makes sense right) | ||||
|       enableSubmit: true, | ||||
| @@ -184,6 +188,9 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     /** | ||||
|      * Grabbed from the store. | ||||
|      */ | ||||
|     ...mapGetters([ | ||||
|                     'transactionType', | ||||
|                     'transactions', | ||||
| @@ -191,9 +198,6 @@ export default { | ||||
|                   ]) | ||||
|   }, | ||||
|   watch: { | ||||
|     transactions: function () { | ||||
|       // console.log('updated transactions'); | ||||
|     }, | ||||
|     submittedTransaction: function () { | ||||
|       // see finalizeSubmit() | ||||
|       this.finalizeSubmit(); | ||||
| @@ -230,18 +234,6 @@ export default { | ||||
|       // console.log('Triggered to remove transaction ' + payload.index); | ||||
|       this.$store.commit('transactions/create/deleteTransaction', payload); | ||||
|     }, | ||||
|     /** | ||||
|      * This method grabs the users preferred custom transaction fields. It's used when configuring the | ||||
|      * custom date selects that will be available. It could be something the component does by itself, | ||||
|      * thereby separating concerns. This is on my list. If it changes to a per-component thing, then | ||||
|      * it should be done via the create.js Vue store because multiple components are interested in the | ||||
|      * user's custom transaction fields. | ||||
|      */ | ||||
|     storeCustomFields: function () { | ||||
|       axios.get('./api/v1/preferences/transaction_journal_optional_fields').then(response => { | ||||
|         this.customFields = response.data.data.attributes.data; | ||||
|       }); | ||||
|     }, | ||||
|     /** | ||||
|      * Submitting a transaction consists of 3 steps: submitting the transaction, uploading attachments | ||||
|      * and creating links. Only once all three steps are executed may the message be shown or the user be | ||||
| @@ -714,7 +706,7 @@ export default { | ||||
|         theDate.setHours(this.time.getHours()); | ||||
|         theDate.setMinutes(this.time.getMinutes()); | ||||
|         theDate.setSeconds(this.time.getSeconds()); | ||||
|         dateStr = this.toW3CString(theDate); | ||||
|         dateStr = toW3CString(theDate); | ||||
|       } | ||||
|  | ||||
|       // console.log('dateStr = ' + dateStr); | ||||
| @@ -829,60 +821,37 @@ export default { | ||||
|       // return it. | ||||
|       return currentSplit; | ||||
|     }, | ||||
|     toW3CString: function (date) { | ||||
|       // https://gist.github.com/tristanlins/6585391 | ||||
|       let year = date.getFullYear(); | ||||
|       let month = date.getMonth(); | ||||
|       month++; | ||||
|       if (month < 10) { | ||||
|         month = '0' + month; | ||||
|       } | ||||
|       let day = date.getDate(); | ||||
|       if (day < 10) { | ||||
|         day = '0' + day; | ||||
|       } | ||||
|       let hours = date.getHours(); | ||||
|       if (hours < 10) { | ||||
|         hours = '0' + hours; | ||||
|       } | ||||
|       let minutes = date.getMinutes(); | ||||
|       if (minutes < 10) { | ||||
|         minutes = '0' + minutes; | ||||
|       } | ||||
|       let seconds = date.getSeconds(); | ||||
|       if (seconds < 10) { | ||||
|         seconds = '0' + seconds; | ||||
|       } | ||||
|       let offset = -date.getTimezoneOffset(); | ||||
|       let offsetHours = Math.abs(Math.floor(offset / 60)); | ||||
|       let offsetMinutes = Math.abs(offset) - offsetHours * 60; | ||||
|       if (offsetHours < 10) { | ||||
|         offsetHours = '0' + offsetHours; | ||||
|       } | ||||
|       if (offsetMinutes < 10) { | ||||
|         offsetMinutes = '0' + offsetMinutes; | ||||
|       } | ||||
|       let offsetSign = '+'; | ||||
|       if (offset < 0) { | ||||
|         offsetSign = '-'; | ||||
|       } | ||||
|       return year + '-' + month + '-' + day + | ||||
|              'T' + hours + ':' + minutes + ':' + seconds + | ||||
|              offsetSign + offsetHours + ':' + offsetMinutes; | ||||
|     }, | ||||
|     storeAllowedOpposingTypes: function () { | ||||
|     /** | ||||
|      * Get API value. | ||||
|      */ | ||||
|     getAllowedOpposingTypes: function () { | ||||
|       axios.get('./api/v1/configuration/static/firefly.allowed_opposing_types') | ||||
|           .then(response => { | ||||
|             this.allowedOpposingTypes = response.data['firefly.allowed_opposing_types']; | ||||
|             // console.log('Set allowedOpposingTypes'); | ||||
|           }); | ||||
|     }, | ||||
|     storeAccountToTransaction: function () { | ||||
|     /** | ||||
|      * Get API value. | ||||
|      */ | ||||
|     getAccountToTransaction: function () { | ||||
|       axios.get('./api/v1/configuration/static/firefly.account_to_transaction') | ||||
|           .then(response => { | ||||
|             this.accountToTransaction = response.data['firefly.account_to_transaction']; | ||||
|           }); | ||||
|     }, | ||||
|     /** | ||||
|      * This method grabs the users preferred custom transaction fields. It's used when configuring the | ||||
|      * custom date selects that will be available. It could be something the component does by itself, | ||||
|      * thereby separating concerns. This is on my list. If it changes to a per-component thing, then | ||||
|      * it should be done via the create.js Vue store because multiple components are interested in the | ||||
|      * user's custom transaction fields. | ||||
|      */ | ||||
|     getCustomFields: function () { | ||||
|       axios.get('./api/v1/preferences/transaction_journal_optional_fields').then(response => { | ||||
|         this.customFields = response.data.data.attributes.data; | ||||
|       }); | ||||
|     }, | ||||
|     setDestinationAllowedTypes: function (value) { | ||||
|       // console.log('Create::setDestinationAllowedTypes'); | ||||
|       // console.log(value); | ||||
|   | ||||
| @@ -20,19 +20,545 @@ | ||||
|  | ||||
| <template> | ||||
|   <div> | ||||
|     <alert :message="errorMessage" type="danger"/> | ||||
|     <alert :message="successMessage" type="success"/> | ||||
|     <Alert :message="errorMessage" type="danger"/> | ||||
|     <Alert :message="successMessage" type="success"/> | ||||
|     <SplitPills :transactions="transactions"/> | ||||
|  | ||||
|     <div class="tab-content"> | ||||
|       <SplitForm | ||||
|           v-for="(transaction, index) in this.transactions" | ||||
|           v-bind:key="index" | ||||
|           :count="transactions.length" | ||||
|           :transaction="transaction" | ||||
|           :allowed-opposing-types="allowedOpposingTypes" | ||||
|           :custom-fields="customFields" | ||||
|           :date="date" | ||||
|           :time="time" | ||||
|           :index="index" | ||||
|           :transaction-type="transactionType" | ||||
|           :destination-allowed-types="destinationAllowedTypes" | ||||
|           :source-allowed-types="sourceAllowedTypes" | ||||
|           :allow-switch="false" | ||||
|           :submitted-transaction="submittedTransaction" | ||||
|           v-on:uploaded-attachments="uploadedAttachment($event)" | ||||
|           v-on:set-marker-location="storeLocation($event)" | ||||
|           v-on:set-account="storeAccountValue($event)" | ||||
|           v-on:set-date="storeDate($event)" | ||||
|           v-on:set-time="storeTime($event)" | ||||
|           v-on:set-field="storeField($event)" | ||||
|           v-on:remove-transaction="removeTransaction($event)" | ||||
|           v-on:selected-attachments="selectedAttachments($event)" | ||||
|       /> | ||||
|     </div> | ||||
|  | ||||
|     <!-- bottom buttons etc --> | ||||
|     <div class="row"> | ||||
|       <!-- group title --> | ||||
|       <div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12"> | ||||
|         <div v-if="transactions.length > 1" class="card"> | ||||
|           <div class="card-body"> | ||||
|             <div class="row"> | ||||
|               <div class="col"> | ||||
|                 <TransactionGroupTitle v-model="this.groupTitle" :errors="this.groupTitleErrors" v-on:set-group-title="storeGroupTitle($event)"/> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="col-xl-6 col-lg-6 col-md-12 col-sm-12 col-xs-12"> | ||||
|         <!-- buttons --> | ||||
|         <div class="card"> | ||||
|           <div class="card-body"> | ||||
|             <div class="row"> | ||||
|               <div class="col"> | ||||
|                 <div class="text-xs d-none d-lg-block d-xl-block"> | ||||
|                     | ||||
|                 </div> | ||||
|                 <button class="btn btn-outline-primary btn-block" @click="addTransaction"><i class="far fa-clone"></i> {{ $t('firefly.add_another_split') }} | ||||
|                 </button> | ||||
|               </div> | ||||
|               <div class="col"> | ||||
|                 <div class="text-xs d-none d-lg-block d-xl-block"> | ||||
|                     | ||||
|                 </div> | ||||
|                 <button :disabled="!enableSubmit" class="btn btn-info btn-block" @click="submitTransaction"> | ||||
|                   <span v-if="enableSubmit"><i class="far fa-save"></i> {{ $t('firefly.update_transaction') }}</span> | ||||
|                   <span v-if="!enableSubmit"><i class="fas fa-spinner fa-spin"></i></span> | ||||
|                 </button> | ||||
|               </div> | ||||
|             </div> | ||||
|             <div class="row"> | ||||
|               <div class="col"> | ||||
|                   | ||||
|               </div> | ||||
|               <div class="col"> | ||||
|                 <div class="form-check"> | ||||
|                   <input id="createAnother" v-model="createAnother" class="form-check-input" type="checkbox"> | ||||
|                   <label class="form-check-label" for="createAnother"> | ||||
|                     <span class="small">{{ $t('firefly.after_update_create_another') }}</span> | ||||
|                   </label> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| const lodashClonedeep = require('lodash.clonedeep'); | ||||
| import Alert from '../partials/Alert'; | ||||
| import SplitPills from "./SplitPills"; | ||||
| import SplitForm from "./SplitForm"; | ||||
| import TransactionGroupTitle from "./TransactionGroupTitle"; | ||||
| import {getDefaultErrors, getDefaultTransaction, toW3CString} from '../shared/transactions'; | ||||
|  | ||||
| export default { | ||||
|   name: "Edit", | ||||
|   created() { | ||||
|     let parts = window.location.pathname.split('/'); | ||||
|     this.groupId = parseInt(parts[parts.length - 1]); | ||||
|  | ||||
|     this.getTransactionGroup(); | ||||
|     this.getAllowedOpposingTypes(); | ||||
|     this.getCustomFields(); | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       successMessage: '', | ||||
|       errorMessage: '', | ||||
|  | ||||
|       // transaction props | ||||
|       transactions: [], | ||||
|       originalTransactions: [], | ||||
|       groupTitle: '', | ||||
|       originalGroupTitle: '', | ||||
|       transactionType: 'any', | ||||
|       groudId: 0, | ||||
|  | ||||
|       // errors in the group title: | ||||
|       groupTitleErrors: [], | ||||
|  | ||||
|       // which custom fields to show TODO | ||||
|       customFields: {}, | ||||
|  | ||||
|       // date and time of the transaction, TODO | ||||
|       date: new Date, | ||||
|       time: new Date, | ||||
|       originalDate: new Date, | ||||
|       originalTime: new Date, | ||||
|  | ||||
|       // things the process is done working on (3 phases): | ||||
|       submittedTransaction: false, | ||||
|       submittedLinks: false, | ||||
|       submittedAttachments: false, | ||||
|  | ||||
|       // meta data for accounts | ||||
|       allowedOpposingTypes: {}, | ||||
|       destinationAllowedTypes: [], | ||||
|       sourceAllowedTypes: [], | ||||
|  | ||||
|       // states for the form (makes sense right) | ||||
|       enableSubmit: true, | ||||
|       createAnother: false, | ||||
|       resetFormAfter: false, | ||||
|  | ||||
|     } | ||||
|   }, | ||||
|   components: { | ||||
|     Alert, | ||||
|     SplitPills, | ||||
|     SplitForm, | ||||
|     TransactionGroupTitle | ||||
|   }, | ||||
|   methods: { | ||||
|     /** | ||||
|      * Grap transaction group from URL and submit GET. | ||||
|      */ | ||||
|     getTransactionGroup: function () { | ||||
|       axios.get('./api/v1/transactions/' + this.groupId) | ||||
|           .then(response => { | ||||
|                   this.parseTransactionGroup(response.data); | ||||
|                 } | ||||
|           ).catch(error => { | ||||
|         // console.log('I failed :('); | ||||
|         // console.log(error); | ||||
|       }); | ||||
|     }, | ||||
|     /** | ||||
|      * Parse transaction group. Title is easy, transactions have their own method. | ||||
|      * @param response | ||||
|      */ | ||||
|     parseTransactionGroup: function (response) { | ||||
|       // console.log('Will now parse'); | ||||
|       // console.log(response); | ||||
|       let attributes = response.data.attributes; | ||||
|       let transactions = attributes.transactions.reverse(); | ||||
|       this.groupTitle = attributes.group_title; | ||||
|       this.originalGroupTitle = attributes.group_title; | ||||
|  | ||||
|       for (let i in transactions) { | ||||
|         if (transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) { | ||||
|           let result = this.parseTransaction(parseInt(i), transactions[i]); | ||||
|           this.transactions.push(result); | ||||
|           this.originalTransactions.push(lodashClonedeep(result)); | ||||
|           // pick up the links of this transaction: | ||||
|           this.parseLinks(parseInt(result.transaction_journal_id), parseInt(i)); | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     /** | ||||
|      * Parse a single transaction. | ||||
|      * | ||||
|      * @param index | ||||
|      * @param array | ||||
|      */ | ||||
|     parseTransaction: function (index, array) { | ||||
|       //console.log('index: ' + index); | ||||
|       if (0 === index) { | ||||
|         this.transactionType = array.type.charAt(0).toUpperCase() + array.type.slice(1); | ||||
|         this.sourceAllowedTypes = [array.source_type]; | ||||
|         this.destinationAllowedTypes = [array.destination_type]; | ||||
|         this.date = new Date(array.date); | ||||
|         this.time = new Date(array.date); | ||||
|         this.originalDate = new Date(array.date); | ||||
|         this.originalTime = new Date(array.date); | ||||
|       } | ||||
|       let result = getDefaultTransaction(); | ||||
|       // parsing here: | ||||
|       result.description = array.description; | ||||
|       result.transaction_journal_id = parseInt(array.transaction_journal_id); | ||||
|       // accounts: | ||||
|       result.source_account_id = array.source_id; | ||||
|       result.source_account_name = array.source_name; | ||||
|       result.source_account_type = array.source_type; | ||||
|  | ||||
|       result.destination_account_id = array.destination_id; | ||||
|       result.destination_account_name = array.destination_name; | ||||
|       result.destination_account_type = array.destination_type; | ||||
|  | ||||
|       // amount: | ||||
|       result.amount = array.amount; | ||||
|       result.currency_id = array.currency_id; | ||||
|       result.foreign_amount = array.foreign_amount; | ||||
|       result.foreign_currency_id = array.foreign_currency_id; | ||||
|  | ||||
|       // meta data | ||||
|       result.category = array.category_name; | ||||
|       result.budget_id = array.budget_id; | ||||
|       result.bill_id = array.bill_id; | ||||
|  | ||||
|       result.tags = array.tags; | ||||
|  | ||||
|       // optional date fields (6x): | ||||
|       result.interest_date = array.interest_date ? array.interest_date.substr(0, 10) : ''; | ||||
|       result.book_date = array.book_date ? array.book_date.substr(0, 10) : ''; | ||||
|       result.process_date = array.process_date ? array.process_date.substr(0, 10) : ''; | ||||
|       result.due_date = array.due_date ? array.due_date.substr(0, 10) : ''; | ||||
|       result.payment_date = array.payment_date ? array.payment_date.substr(0, 10) : ''; | ||||
|       result.invoice_date = array.invoice_date ? array.invoice_date.substr(0, 10) : ''; | ||||
|  | ||||
|       // optional other fields: | ||||
|       result.internal_reference = array.internal_reference; | ||||
|       result.external_url = array.external_uri; | ||||
|       result.external_id = array.external_id; | ||||
|       result.notes = array.notes; | ||||
|       // location: | ||||
|       result.location = { | ||||
|         zoom_level: array.zoom_level, | ||||
|         longitude: array.longitude, | ||||
|         latitude: array.latitude, | ||||
|       }; | ||||
|       result.zoom_level = array.zoom_level; | ||||
|       result.longitude = array.longitude; | ||||
|       result.latitude = array.latitude; | ||||
|  | ||||
|       // error handling | ||||
|       result.errors = getDefaultErrors(); | ||||
|       return result; | ||||
|     }, | ||||
|     /** | ||||
|      * Get the links of this transaction group from the API. | ||||
|      */ | ||||
|     parseLinks: function (journalId, index) { | ||||
|       axios.get('./api/v1/transactions/' + journalId + '/links') | ||||
|           .then(response => { | ||||
|             let links = response.data.data; | ||||
|             for (let i in links) { | ||||
|               if (links.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) { | ||||
|                 this.parseLink(links[i], journalId, index); | ||||
|               } | ||||
|             } | ||||
|           }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Process individual link from the API. | ||||
|      */ | ||||
|     parseLink: function (link, journalId, index) { | ||||
|       let promises = []; | ||||
|       let opposingId = parseInt(link.attributes.inward_id); | ||||
|       let linkDirection = 'inward'; | ||||
|       if (opposingId === journalId) { | ||||
|         opposingId = parseInt(link.attributes.outward_id); | ||||
|         linkDirection = 'outward'; | ||||
|       } | ||||
|       // add meta data to promise context. | ||||
|       promises.push(new Promise((resolve) => { | ||||
|         resolve( | ||||
|             { | ||||
|               link: link, | ||||
|               journalId: journalId, | ||||
|               opposingId: opposingId, | ||||
|               index: index, | ||||
|               direction: linkDirection | ||||
|             } | ||||
|         ); | ||||
|       })); | ||||
|  | ||||
|       // get stuff from the API: | ||||
|       promises.push(axios.get('./api/v1/transaction-journals/' + opposingId)); | ||||
|       promises.push(axios.get('./api/v1/transaction_links/' + link.attributes.link_type_id)); | ||||
|  | ||||
|       Promise.all(promises).then(responses => { | ||||
|         let journals = responses[1].data.data.attributes.transactions; | ||||
|         let opposingId = responses[0].opposingId; | ||||
|         let journal = {}; | ||||
|         // loop over journals to get the correct one: | ||||
|         for (let i in journals) { | ||||
|           if (journals.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) { | ||||
|             if (journals[i].transaction_journal_id === opposingId) { | ||||
|               journal = journals[i]; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         let index = responses[0].index; | ||||
|         let direction = responses[0].direction; | ||||
|         let linkTypeId = responses[2].data.data.id; | ||||
|         let object = { | ||||
|           link_type_id: linkTypeId + '-' + direction, | ||||
|           transaction_group_id: responses[1].data.data.id, | ||||
|           transaction_journal_id: journal.transaction_journal_id, | ||||
|           description: journal.description, | ||||
|           type: journal.type, | ||||
|           currency_code: journal.currency_code, | ||||
|           amount: journal.amount | ||||
|         }; | ||||
|         this.transactions[index].links.push(object); | ||||
|         this.originalTransactions[index].links.push(object); | ||||
|       }); | ||||
|     }, | ||||
|     /** | ||||
|      * Get API value. | ||||
|      */ | ||||
|     getAllowedOpposingTypes: function () { | ||||
|       axios.get('./api/v1/configuration/static/firefly.allowed_opposing_types') | ||||
|           .then(response => { | ||||
|             this.allowedOpposingTypes = response.data['firefly.allowed_opposing_types']; | ||||
|             // console.log('Set allowedOpposingTypes'); | ||||
|           }); | ||||
|     }, | ||||
|     /** | ||||
|      * Get API value. | ||||
|      */ | ||||
|     getCustomFields: function () { | ||||
|       axios.get('./api/v1/preferences/transaction_journal_optional_fields').then(response => { | ||||
|         this.customFields = response.data.data.attributes.data; | ||||
|       }); | ||||
|     }, | ||||
|     uploadedAttachment: function (payload) { | ||||
|       console.log('event: uploadedAttachment'); | ||||
|       console.log(payload); | ||||
|     }, | ||||
|     storeLocation: function (payload) { | ||||
|       this.transactions[payload.index].zoom_level = payload.zoomLevel; | ||||
|       this.transactions[payload.index].longitude = payload.lng; | ||||
|       this.transactions[payload.index].latitude = payload.lat; | ||||
|     }, | ||||
|     storeAccountValue: function (payload) { | ||||
|       let direction = payload.direction; | ||||
|       let index = payload.index; | ||||
|       this.transactions[index][direction + '_account_id'] = payload.id; | ||||
|       this.transactions[index][direction + '_account_type'] = payload.type; | ||||
|       this.transactions[index][direction + '_account_name'] = payload.name; | ||||
|     }, | ||||
|     storeDate: function (payload) { | ||||
|       // console.log('event: storeDate'); | ||||
|       // console.log(payload); | ||||
|       this.date = payload.date; | ||||
|     }, | ||||
|     storeTime: function (payload) { | ||||
|       this.time = payload.time; | ||||
|       // console.log('event: storeTime'); | ||||
|       // console.log(payload); | ||||
|     }, | ||||
|     storeField: function (payload) { | ||||
|       let field = payload.field; | ||||
|       if ('category' === field) { | ||||
|         field = 'category_name'; | ||||
|       } | ||||
|       // console.log('event: storeField(' + field + ')'); | ||||
|       this.transactions[payload.index][field] = payload.value; | ||||
|  | ||||
|     }, | ||||
|     removeTransaction: function (payload) { | ||||
|       this.transactions.splice(payload.index, 1); | ||||
|       // this kills the original transactions. | ||||
|       this.originalTransactions = []; | ||||
|     }, | ||||
|     storeGroupTitle: function (payload) { | ||||
|       this.groupTitle = payload; | ||||
|     }, | ||||
|     selectedAttachments: function (payload) { | ||||
|       for (let i in this.transactions) { | ||||
|         if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) { | ||||
|           if (parseInt(this.transactions[i].transaction_journal_id) === parseInt(payload)) { | ||||
|             // console.log('selectedAttachments ' + payload); | ||||
|             this.transactions[i].selectedAttachments = true; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     addTransaction: function () { | ||||
|       let newTransaction = getDefaultTransaction(); | ||||
|       newTransaction.errors = getDefaultErrors(); | ||||
|       this.transactions.push(newTransaction); | ||||
|     }, | ||||
|     submitTransaction: function () { | ||||
|       let submission = {transactions: []}; | ||||
|       let shouldSubmit = false; | ||||
|       let shouldLinks = false; | ||||
|       let shouldUpload = false; | ||||
|       if (this.groupTitle !== this.originalGroupTitle) { | ||||
|         submission.group_title = this.groupTitle; | ||||
|         shouldSubmit = true; | ||||
|       } | ||||
|       for (let i in this.transactions) { | ||||
|         if (this.transactions.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) { | ||||
|           // original transaction present? | ||||
|           let currentTransaction = this.transactions[i]; | ||||
|           let originalTransaction = this.originalTransactions.hasOwnProperty(i) ? this.originalTransactions[i] : {}; | ||||
|  | ||||
|           let diff = {}; | ||||
|  | ||||
|           // compare basic fields: | ||||
|           let basicFields = [ | ||||
|             'description', | ||||
|             'source_account_id', 'source_account_name', | ||||
|             'destination_account_id', 'destination_account_name', | ||||
|             'amount', 'foreign_amount', 'foreign_currency_id', | ||||
|             'category_name', 'budget_id', 'bill_id', | ||||
|             'interest_date', 'book_date', 'due_date', 'payment_date', 'invoice_date', | ||||
|             'external_url', 'internal_reference', 'external_id', 'notes', | ||||
|             'zoom_level', 'longitude', 'latitude' | ||||
|           ]; | ||||
|  | ||||
|           for (let ii in basicFields) { | ||||
|             if (basicFields.hasOwnProperty(ii) && /^0$|^[1-9]\d*$/.test(ii) && ii <= 4294967294) { | ||||
|               let fieldName = basicFields[ii]; | ||||
|               if (currentTransaction[fieldName] !== originalTransaction[fieldName]) { | ||||
|                 // console.log('Index ' + i + ': Field ' + fieldName + ' updated ("' + originalTransaction[fieldName] + '" > "' + currentTransaction[fieldName] + '")'); | ||||
|                 // console.log(originalTransaction[fieldName]); | ||||
|                 // console.log(currentTransaction[fieldName]); | ||||
|                 diff[fieldName] = currentTransaction[fieldName]; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|           if (0 !== currentTransaction.piggy_bank_id) { | ||||
|             diff.piggy_bank_id = currentTransaction.piggy_bank_id; | ||||
|           } | ||||
|           if (JSON.stringify(currentTransaction.tags) !== JSON.stringify(originalTransaction.tags)) { | ||||
|             // console.log('tags are different'); | ||||
|             // console.log(currentTransaction.tags); | ||||
|             // console.log(originalTransaction.tags); | ||||
|             diff.tags = currentTransaction.tags; | ||||
|           } | ||||
|  | ||||
|           // compare links: | ||||
|           let newLinks = this.compareLinks(currentTransaction.links); | ||||
|           let originalLinks = this.compareLinks(originalTransaction.links); | ||||
|           // console.log('links are?'); | ||||
|           // console.log(newLinks); | ||||
|           // console.log(originalLinks); | ||||
|           if (newLinks !== originalLinks) { | ||||
|             // console.log('links are different!'); | ||||
|             // console.log(newLinks); | ||||
|             // console.log(originalLinks); | ||||
|             shouldLinks = true; | ||||
|           } | ||||
|           // this.transactions[i].selectedAttachments | ||||
|           // console.log(typeof currentTransaction.selectedAttachments); | ||||
|           // console.log(currentTransaction.selectedAttachments); | ||||
|           if (typeof currentTransaction.selectedAttachments !== 'undefined' && true === currentTransaction.selectedAttachments) { | ||||
|             // must upload! | ||||
|             shouldUpload = true; | ||||
|           } | ||||
|  | ||||
|           let dateStr = 'invalid'; | ||||
|           if ( | ||||
|               this.date.toISOString() !== this.originalDate.toISOString() || | ||||
|               this.time.toISOString() !== this.originalTime.toISOString() | ||||
|           ) { | ||||
|             // set date and time! | ||||
|             shouldSubmit = true; | ||||
|             let theDate = this.date; | ||||
|             // update time in date object. | ||||
|             theDate.setHours(this.time.getHours()); | ||||
|             theDate.setMinutes(this.time.getMinutes()); | ||||
|             theDate.setSeconds(this.time.getSeconds()); | ||||
|             dateStr = toW3CString(theDate); | ||||
|             submission.date = dateStr; | ||||
|           } | ||||
|  | ||||
|           if (Object.keys(diff).length !== 0) { | ||||
|             diff.transaction_journal_id = originalTransaction.transaction_journal_id; | ||||
|             submission.transactions.push(diff); | ||||
|             shouldSubmit = true; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       console.log('submitTransaction'); | ||||
|       console.log(shouldUpload); | ||||
|       console.log(shouldLinks); | ||||
|       console.log(shouldSubmit); | ||||
|       if (shouldSubmit) { | ||||
|         this.submitUpdate(submission); | ||||
|       } | ||||
|       console.log(submission); | ||||
|     }, | ||||
|     compareLinks: function (array) { | ||||
|       let compare = []; | ||||
|       for (let i in array) { | ||||
|         if (array.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) { | ||||
|           compare.push( | ||||
|               { | ||||
|                 amount: array[i].amount, | ||||
|                 currency_code: array[i].currency_code, | ||||
|                 description: array[i].description, | ||||
|                 link_type_id: array[i].link_type_id, | ||||
|                 transaction_group_id: array[i].transaction_group_id, | ||||
|                 type: array[i].type, | ||||
|               } | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|       // console.log('compareLinks'); | ||||
|       // console.log(compare); | ||||
|       return JSON.stringify(compare); | ||||
|     }, | ||||
|     submitUpdate: function (submission) { | ||||
|       const url = './api/v1/transactions/' + this.groupId; | ||||
|       axios.put(url, submission) | ||||
|           .then(response => { | ||||
|                   console.log('OK!'); | ||||
|                 } | ||||
|           ).catch(error => { | ||||
|         console.log('error :('); | ||||
|         console.log(error); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -62,7 +62,7 @@ | ||||
|               <!-- switcharoo! --> | ||||
|               <div class="col-xl-2 col-lg-2 col-md-2 col-sm-12 text-center d-none d-sm-block"> | ||||
|                 <SwitchAccount | ||||
|                     v-if="0 === index" | ||||
|                     v-if="0 === index && allowSwitch" | ||||
|                     v-on="$listeners" | ||||
|                     :index="index" | ||||
|                     :transaction-type="transactionType" | ||||
| @@ -272,7 +272,7 @@ | ||||
|                     :transaction_journal_id="transaction.transaction_journal_id" | ||||
|                 /> | ||||
|                 <TransactionLocation | ||||
|                     v-model="transaction.notes" | ||||
|                     v-model="transaction.location" | ||||
|                     v-on="$listeners" | ||||
|                     :custom-fields.sync="customFields" | ||||
|                     :errors="transaction.errors.location" | ||||
| @@ -323,20 +323,63 @@ import TransactionLocation from "./TransactionLocation"; | ||||
|  | ||||
| export default { | ||||
|   name: "SplitForm", | ||||
|   props: [ | ||||
|     'transaction', | ||||
|     'split', | ||||
|     'count', | ||||
|     'customFields', // for custom transaction form fields. | ||||
|     'index', | ||||
|     'date', | ||||
|     'time', | ||||
|     'transactionType', | ||||
|     'submittedTransaction', // need to know if transaction is submitted. | ||||
|     'sourceAllowedTypes', // allowed source account types. | ||||
|     'destinationAllowedTypes', | ||||
|     'allowedOpposingTypes' | ||||
|   ], | ||||
|   props: { | ||||
|     transaction: { | ||||
|       type: Object, | ||||
|       required: true | ||||
|     }, | ||||
|     count: { | ||||
|       type: Number, | ||||
|       required: false | ||||
|     }, | ||||
|     customFields: { | ||||
|       type: Object, | ||||
|       required: false | ||||
|     }, | ||||
|     index: { | ||||
|       type: Number, | ||||
|       required: true | ||||
|     }, | ||||
|     date: { | ||||
|       type: Date, | ||||
|       required: true | ||||
|     }, | ||||
|     time: { | ||||
|       type: Date, | ||||
|       required: true | ||||
|     }, | ||||
|     transactionType: { | ||||
|       type: String, | ||||
|       required: true | ||||
|     }, | ||||
|     submittedTransaction: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false | ||||
|     }, // need to know if transaction is submitted. | ||||
|     sourceAllowedTypes: { | ||||
|       type: Array, | ||||
|       required: false, | ||||
|       default: [] | ||||
|     }, // allowed source account types. | ||||
|     destinationAllowedTypes: { | ||||
|       type: Array, | ||||
|       required: false, | ||||
|       default: [] | ||||
|     }, | ||||
|     allowedOpposingTypes: { | ||||
|       type: Object, | ||||
|       required: false, | ||||
|       default: {} | ||||
|     }, | ||||
|     // allow switch? | ||||
|     allowSwitch: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: true | ||||
|     } | ||||
|  | ||||
|   }, | ||||
|   // watch: { | ||||
|   //   allowedOpposingTypes: function() { | ||||
|   //     console.log('SplitForm noticed change in allowedOpposingTypes'); | ||||
|   | ||||
| @@ -91,6 +91,8 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.selectedAccountTrigger = true; | ||||
|     this.accountName = this.account.name ?? ''; | ||||
|     this.createInitialSet(); | ||||
|   }, | ||||
|   methods: { | ||||
|   | ||||
| @@ -45,25 +45,48 @@ | ||||
|  | ||||
| export default { | ||||
|   name: "TransactionAmount", | ||||
|   props: [ | ||||
|     'index', | ||||
|     'errors', | ||||
|     'amount', | ||||
|     'transactionType', | ||||
|     'sourceCurrencySymbol', | ||||
|     'destinationCurrencySymbol', | ||||
|   ], | ||||
|   props: { | ||||
|     index: { | ||||
|       type: Number, | ||||
|       default: 0, | ||||
|       required: true | ||||
|     }, | ||||
|     errors: {}, | ||||
|     amount: {}, | ||||
|     transactionType: {}, | ||||
|     sourceCurrencySymbol: {}, | ||||
|     destinationCurrencySymbol: {}, | ||||
|     fractionDigits: { | ||||
|       default: 2, | ||||
|       required: false | ||||
|     }, | ||||
|   }, | ||||
|   created() { | ||||
|     if ('' !== this.amount) { | ||||
|       this.emitEvent = false; | ||||
|       this.transactionAmount = this.formatNumber(this.amount); | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     formatNumber(str) { | ||||
|       return parseFloat(str).toFixed(this.fractionDigits); | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       transactionAmount: this.amount, | ||||
|       currencySymbol: null, | ||||
|       srcCurrencySymbol: this.sourceCurrencySymbol, | ||||
|       dstCurrencySymbol: this.destinationCurrencySymbol, | ||||
|       emitEvent: true | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     transactionAmount: function (value) { | ||||
|       this.$emit('set-field', {field: 'amount', index: this.index, value: value}); | ||||
|       if (true === this.emitEvent) { | ||||
|         this.$emit('set-field', {field: 'amount', index: this.index, value: value}); | ||||
|       } | ||||
|       this.emitEvent = true; | ||||
|     }, | ||||
|     amount: function (value) { | ||||
|       this.transactionAmount = value; | ||||
|   | ||||
| @@ -28,6 +28,7 @@ | ||||
|           ref="att" | ||||
|           class="form-control" | ||||
|           multiple | ||||
|           @change="selectedFile" | ||||
|           name="attachments[]" | ||||
|           type="file" | ||||
|       /> | ||||
| @@ -70,6 +71,9 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     selectedFile: function() { | ||||
|       this.$emit('selected-attachments', this.transaction_journal_id); | ||||
|     }, | ||||
|     doUpload: function () { | ||||
|       // console.log('Now in doUpload() for ' + this.$refs.att.files.length + ' files.'); | ||||
|       for (let i in this.$refs.att.files) { | ||||
|   | ||||
| @@ -50,6 +50,7 @@ | ||||
|     <span v-if="errors.length > 0"> | ||||
|       <span v-for="error in errors" class="text-danger small">{{ error }}<br/></span> | ||||
|     </span> | ||||
|     <span class="text-muted small" v-if="'' !== timeZone">{{ timeZone }}</span> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| @@ -58,10 +59,14 @@ | ||||
| export default { | ||||
|   props: ['index', 'errors', 'date', 'time'], | ||||
|   name: "TransactionDate", | ||||
|   created() { | ||||
|     this.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       localDate: this.date, | ||||
|       localTime: this.time | ||||
|       localTime: this.time, | ||||
|       timeZone: '' | ||||
|     } | ||||
|   }, | ||||
|   methods: {}, | ||||
|   | ||||
| @@ -42,25 +42,46 @@ | ||||
| <script> | ||||
| export default { | ||||
|   name: "TransactionForeignAmount", | ||||
|   props: [ | ||||
|     'index', | ||||
|     'errors', | ||||
|     'value', | ||||
|     'transactionType', | ||||
|     'sourceCurrencyId', | ||||
|     'destinationCurrencyId' | ||||
|   ], | ||||
|   props: { | ||||
|     index: {}, | ||||
|     errors: {}, | ||||
|     value: {}, | ||||
|     transactionType: {}, | ||||
|     sourceCurrencyId: {}, | ||||
|     destinationCurrencyId: {}, | ||||
|     fractionDigits: { | ||||
|       type: Number, | ||||
|       default: 2 | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       amount: this.value | ||||
|       amount: this.value, | ||||
|       emitEvent: true | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     if ('' !== this.amount) { | ||||
|       this.emitEvent = false; | ||||
|       this.amount = this.formatNumber(this.amount); | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     formatNumber(str) { | ||||
|       return parseFloat(str).toFixed(this.fractionDigits); | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     amount: function (value) { | ||||
|       this.$emit('set-field', {field: 'foreign_amount', index: this.index, value: value}); | ||||
|       if (true === this.emitEvent) { | ||||
|         this.$emit('set-field', {field: 'foreign_amount', index: this.index, value: value}); | ||||
|       } | ||||
|       this.emitEvent = true; | ||||
|     }, | ||||
|     value: function (value) { | ||||
|       this.amount = value; | ||||
|  | ||||
|  | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|   | ||||
| @@ -27,10 +27,10 @@ | ||||
|       <div class="row"> | ||||
|         <div class="col"> | ||||
|           <p v-if="links.length === 0"> | ||||
|             <button class="btn btn-default btn-xs" data-target="#linkModal" data-toggle="modal"><i class="fas fa-plus"></i> Add transaction link</button> | ||||
|             <button class="btn btn-default btn-xs" data-target="#linkModal" @click="resetModal" data-toggle="modal"><i class="fas fa-plus"></i> Add transaction link</button> | ||||
|           </p> | ||||
|           <ul v-if="links.length > 0" class="list-group"> | ||||
|             <li v-for="transaction in links" class="list-group-item"> | ||||
|             <li v-for="(transaction, index) in links" class="list-group-item" v-bind:key="index"> | ||||
|               <em>{{ getTextForLinkType(transaction.link_type_id) }}</em> | ||||
|               <a :href='"./transaction/show/" + transaction.transaction_group_id'>{{ transaction.description }}</a> | ||||
|  | ||||
| @@ -59,19 +59,18 @@ | ||||
|                 }}</span>) | ||||
|                         </span> | ||||
|               <div class="btn-group btn-group-xs float-right"> | ||||
|                 <a class="btn btn-xs btn-default" href="#" tabindex="-1"><i class="far fa-edit"></i></a> | ||||
|                 <a class="btn btn-xs btn-danger" href="#" tabindex="-1"><i class="far fa-trash-alt"></i></a> | ||||
|                 <button class="btn btn-xs btn-danger" @click="removeLink(index)" tabindex="-1"><i class="far fa-trash-alt"></i></button> | ||||
|               </div> | ||||
|             </li> | ||||
|           </ul> | ||||
|           <div v-if="links.length > 0" class="form-text"> | ||||
|             <button class="btn btn-default" data-target="#linkModal" data-toggle="modal"><i class="fas fa-plus"></i></button> | ||||
|             <button class="btn btn-default" @click="resetModal" data-target="#linkModal" data-toggle="modal"><i class="fas fa-plus"></i></button> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <!-- modal --> | ||||
|     <div id="linkModal" class="modal" tabindex="-1"> | ||||
|     <div id="linkModal" class="modal" tabindex="-1" ref="linkModal"> | ||||
|       <div class="modal-dialog modal-lg"> | ||||
|         <div class="modal-content"> | ||||
|           <div class="modal-header"> | ||||
| @@ -204,8 +203,10 @@ export default { | ||||
|   }, | ||||
|   created() { | ||||
|     this.locale = localStorage.locale ?? 'en-US'; | ||||
|     this.emitEvent = false; | ||||
|     this.links = lodashClonedeep(this.value); | ||||
|     this.getLinkTypes(); | ||||
|  | ||||
|   }, | ||||
|   computed: { | ||||
|     showField: function () { | ||||
| @@ -217,8 +218,10 @@ export default { | ||||
|   }, | ||||
|   watch: { | ||||
|     value: function (value) { | ||||
|       this.emitEvent = false; | ||||
|       this.links = lodashClonedeep(value); | ||||
|       if (null !== value) { | ||||
|         this.emitEvent = false; | ||||
|         this.links = lodashClonedeep(value); | ||||
|       } | ||||
|     }, | ||||
|     links: function (value) { | ||||
|       if (true === this.emitEvent) { | ||||
| @@ -231,6 +234,9 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     removeLink: function (index) { | ||||
|       this.links.splice(index, 1); | ||||
|     }, | ||||
|     getTextForLinkType: function (linkTypeId) { | ||||
|       let parts = linkTypeId.split('-'); | ||||
|       for (let i in this.linkTypes) { | ||||
| @@ -299,6 +305,9 @@ export default { | ||||
|                 } | ||||
|           ); | ||||
|     }, | ||||
|     resetModal: function() { | ||||
|       this.search(); | ||||
|     }, | ||||
|     parseLinkTypes: function (data) { | ||||
|       for (let i in data.data) { | ||||
|         if (data.data.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) { | ||||
| @@ -323,6 +332,10 @@ export default { | ||||
|       } | ||||
|     }, | ||||
|     search: function () { | ||||
|       if('' === this.query) { | ||||
|         this.searchResults = []; | ||||
|         return; | ||||
|       } | ||||
|       this.searching = true; | ||||
|       this.searchResults = []; | ||||
|       let url = './api/v1/search/transactions?limit=10&query=' + this.query; | ||||
|   | ||||
| @@ -64,22 +64,41 @@ L.Icon.Default.mergeOptions({ | ||||
|  | ||||
| export default { | ||||
|   name: "TransactionLocation", | ||||
|   props: ['index', 'value', 'errors', 'customFields'], | ||||
|   props: { | ||||
|     index: {}, | ||||
|     value: { | ||||
|       type: Object, | ||||
|       required: false | ||||
|     }, | ||||
|     errors: {}, | ||||
|     customFields: {}, | ||||
|   }, | ||||
|   components: { | ||||
|     LMap, | ||||
|     LTileLayer, | ||||
|     LMarker, | ||||
|   }, | ||||
|   created() { | ||||
|     axios.get('./api/v1/configuration/static/firefly.default_location').then(response => { | ||||
|       this.zoom = parseInt(response.data['firefly.default_location'].zoom_level); | ||||
|       this.center = | ||||
|           [ | ||||
|             parseFloat(response.data['firefly.default_location'].latitude), | ||||
|             parseFloat(response.data['firefly.default_location'].longitude), | ||||
|           ] | ||||
|       ; | ||||
|     }); | ||||
|     if (null === this.value || typeof this.value === 'undefined') { | ||||
|       axios.get('./api/v1/configuration/static/firefly.default_location').then(response => { | ||||
|         this.zoom = parseInt(response.data['firefly.default_location'].zoom_level); | ||||
|         this.center = | ||||
|             [ | ||||
|               parseFloat(response.data['firefly.default_location'].latitude), | ||||
|               parseFloat(response.data['firefly.default_location'].longitude), | ||||
|             ] | ||||
|         ; | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|     if (null !== this.value.zoom_level && null !== this.value.latitude && null !== this.value.longitude) { | ||||
|       this.zoom = this.value.zoom_level; | ||||
|       this.center = [ | ||||
|         parseFloat(this.value.latitude), | ||||
|         parseFloat(this.value.longitude), | ||||
|       ]; | ||||
|       this.hasMarker = true; | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|   | ||||
| @@ -61,6 +61,16 @@ export default { | ||||
|       emitEvent: true | ||||
|     }; | ||||
|   }, | ||||
|   created() { | ||||
|     let tags = []; | ||||
|     for (let i in this.value) { | ||||
|       if (this.value.hasOwnProperty(i) && /^0$|^[1-9]\d*$/.test(i) && i <= 4294967294) { | ||||
|         tags.push({text: this.value[i]}); | ||||
|       } | ||||
|     } | ||||
|     this.updateTags = false; | ||||
|     this.tags = tags; | ||||
|   }, | ||||
|   watch: { | ||||
|     'currentTag': 'initItems', | ||||
|     value: function (value) { | ||||
| @@ -68,6 +78,7 @@ export default { | ||||
|       this.tagList = value; | ||||
|     }, | ||||
|     tagList: function (value) { | ||||
|       console.log('watch tagList'); | ||||
|       if (true === this.emitEvent) { | ||||
|         this.$emit('set-field', {field: 'tags', index: this.index, value: value}); | ||||
|       } | ||||
|   | ||||
| @@ -29,6 +29,8 @@ use FireflyIII\Http\Middleware\IsAdmin; | ||||
|  * System and configuration controllers | ||||
|  */ | ||||
|  | ||||
| // TODO get rid of underscores. | ||||
|  | ||||
| // ABOUT FIREFLY III | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
| @@ -276,6 +278,7 @@ Route::group( | ||||
| ); | ||||
|  | ||||
| // Categories | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers\Chart', 'prefix' => 'chart/category', | ||||
|      'as'        => 'api.v1.chart.category.',], | ||||
| @@ -286,7 +289,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'cer', | ||||
|      'as'        => 'api.v1.cer.',], | ||||
| @@ -297,6 +300,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'link_types', | ||||
|      'as'        => 'api.v1.link_types.',], | ||||
| @@ -312,6 +316,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'transaction_links', | ||||
|      'as'        => 'api.v1.transaction_links.',], | ||||
| @@ -344,6 +349,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'preferences', | ||||
|      'as'        => 'api.v1.preferences.',], | ||||
| @@ -357,6 +363,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'recurrences', | ||||
|      'as'        => 'api.v1.recurrences.',], | ||||
| @@ -373,6 +380,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'rules', | ||||
|      'as'        => 'api.v1.rules.',], | ||||
| @@ -391,6 +399,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'rule_groups', | ||||
|      'as'        => 'api.v1.rule_groups.',], | ||||
| @@ -411,6 +420,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers\Search', 'prefix' => 'search', | ||||
|      'as'        => 'api.v1.search.',], | ||||
| @@ -422,6 +432,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers\Webhook', 'prefix' => 'webhooks', | ||||
|      'as'        => 'api.v1.webhooks.',], | ||||
| @@ -439,6 +450,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'summary', | ||||
|      'as'        => 'api.v1.summary.',], | ||||
| @@ -450,6 +462,7 @@ Route::group( | ||||
| ); | ||||
|  | ||||
| // destroy data route. | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'data', | ||||
|      'as'        => 'api.v1.data.',], | ||||
| @@ -461,6 +474,7 @@ Route::group( | ||||
| ); | ||||
|  | ||||
| // INSIGHT | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers\Insight', 'prefix' => 'insight', | ||||
|      'as'        => 'api.v1.insight.',], | ||||
| @@ -476,6 +490,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'currencies', | ||||
|      'as'        => 'api.v1.currencies.',], | ||||
| @@ -504,6 +519,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'tags', | ||||
|      'as'        => 'api.v1.tags.',], | ||||
| @@ -520,6 +536,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'tag-cloud', | ||||
|      'as'        => 'api.v1.tag-cloud.',], | ||||
| @@ -529,6 +546,7 @@ Route::group( | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'transactions', | ||||
|      'as'        => 'api.v1.transactions.',], | ||||
| @@ -540,7 +558,7 @@ Route::group( | ||||
|         Route::get('{transactionGroup}', ['uses' => 'Models\Transaction\ShowController@show', 'as' => 'show']); | ||||
|         Route::get('{transactionGroup}/attachments', ['uses' => 'Models\Transaction\ListController@attachments', 'as' => 'attachments']); | ||||
|         Route::get('{transactionGroup}/piggy_bank_events', ['uses' => 'Models\Transaction\ListController@piggyBankEvents', 'as' => 'piggy_bank_events']); | ||||
|         Route::get('{tj}/transaction_links', ['uses' => 'Models\Transaction\ListController@transactionLinks', 'as' => 'transaction_links']); | ||||
|         Route::get('{tj}/links', ['uses' => 'Models\Transaction\ListController@transactionLinks', 'as' => 'transaction_links']); | ||||
|         Route::put('{transactionGroup}', ['uses' => 'Models\Transaction\UpdateController@update', 'as' => 'update']); | ||||
|         Route::delete('{transactionGroup}/{tj}', ['uses' => 'Models\Transaction\DestroyController@destroyJournal', 'as' => 'delete-journal']); | ||||
|         Route::delete('{transactionGroup}', ['uses' => 'Models\Transaction\DestroyController@destroy', 'as' => 'delete']); | ||||
| @@ -548,16 +566,18 @@ Route::group( | ||||
| ); | ||||
|  | ||||
| // special group for transaction journals | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'transaction-journals', | ||||
|     ['namespace' => 'FireflyIII\Api\V1\Controllers\Models\Transaction', 'prefix' => 'transaction-journals', | ||||
|      'as'        => 'api.v1.journals.',], | ||||
|     static function () { | ||||
|  | ||||
|         // Transaction API routes: | ||||
|         Route::get('{tj}', ['uses' => 'TransactionController@showByJournal', 'as' => 'showByJournal']); | ||||
|         Route::get('{tj}', ['uses' => 'ShowController@showByJournal', 'as' => 'showByJournal']); | ||||
|     } | ||||
| ); | ||||
|  | ||||
| // TODO VERIFY API DOCS | ||||
| Route::group( | ||||
|     ['middleware' => ['auth:api', 'bindings', IsAdmin::class], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'users', | ||||
|      'as'         => 'api.v1.users.',], | ||||
|   | ||||
		Reference in New Issue
	
	Block a user