mirror of
				https://github.com/crater-invoice/crater.git
				synced 2025-10-30 21:21:09 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			313 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			313 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <BasePage>
 | |
|     <BasePageHeader :title="pageTitle">
 | |
|       <BaseBreadcrumb>
 | |
|         <BaseBreadcrumbItem :title="$t('general.home')" to="dashboard" />
 | |
|         <BaseBreadcrumbItem :title="$tc('items.item', 2)" to="/admin/items" />
 | |
|         <BaseBreadcrumbItem :title="pageTitle" to="#" active />
 | |
|       </BaseBreadcrumb>
 | |
|     </BasePageHeader>
 | |
| 
 | |
|     <ItemUnitModal />
 | |
| 
 | |
|     <form
 | |
|       class="grid lg:grid-cols-2 mt-6"
 | |
|       action="submit"
 | |
|       @submit.prevent="submitItem"
 | |
|     >
 | |
|       <BaseCard class="w-full">
 | |
|         <BaseInputGrid layout="one-column">
 | |
|           <BaseInputGroup
 | |
|             :label="$t('items.name')"
 | |
|             :content-loading="isFetchingInitialData"
 | |
|             required
 | |
|             :error="
 | |
|               v$.currentItem.name.$error &&
 | |
|               v$.currentItem.name.$errors[0].$message
 | |
|             "
 | |
|           >
 | |
|             <BaseInput
 | |
|               v-model="itemStore.currentItem.name"
 | |
|               :content-loading="isFetchingInitialData"
 | |
|               :invalid="v$.currentItem.name.$error"
 | |
|               @input="v$.currentItem.name.$touch()"
 | |
|             />
 | |
|           </BaseInputGroup>
 | |
| 
 | |
|           <BaseInputGroup
 | |
|             :label="$t('items.price')"
 | |
|             :content-loading="isFetchingInitialData"
 | |
|           >
 | |
|             <BaseMoney
 | |
|               v-model="price"
 | |
|               :content-loading="isFetchingInitialData"
 | |
|             />
 | |
|           </BaseInputGroup>
 | |
| 
 | |
|           <BaseInputGroup
 | |
|             :content-loading="isFetchingInitialData"
 | |
|             :label="$t('items.unit')"
 | |
|           >
 | |
|             <BaseMultiselect
 | |
|               v-model="itemStore.currentItem.unit_id"
 | |
|               :content-loading="isFetchingInitialData"
 | |
|               label="name"
 | |
|               :options="itemStore.itemUnits"
 | |
|               value-prop="id"
 | |
|               :can-deselect="false"
 | |
|               :can-clear="false"
 | |
|               :placeholder="$t('items.select_a_unit')"
 | |
|               searchable
 | |
|               track-by="name"
 | |
|             >
 | |
|               <template #action>
 | |
|                 <BaseSelectAction @click="addItemUnit">
 | |
|                   <BaseIcon
 | |
|                     name="PlusIcon"
 | |
|                     class="h-4 mr-2 -ml-2 text-center text-primary-400"
 | |
|                   />
 | |
|                   {{ $t('settings.customization.items.add_item_unit') }}
 | |
|                 </BaseSelectAction>
 | |
|               </template>
 | |
|             </BaseMultiselect>
 | |
|           </BaseInputGroup>
 | |
| 
 | |
|           <BaseInputGroup
 | |
|             v-if="isTaxPerItem"
 | |
|             :label="$t('items.taxes')"
 | |
|             :content-loading="isFetchingInitialData"
 | |
|           >
 | |
|             <BaseMultiselect
 | |
|               v-model="taxes"
 | |
|               :content-loading="isFetchingInitialData"
 | |
|               :options="getTaxTypes"
 | |
|               mode="tags"
 | |
|               label="tax_name"
 | |
|               class="w-full"
 | |
|               value-prop="id"
 | |
|               :can-deselect="false"
 | |
|               :can-clear="false"
 | |
|               searchable
 | |
|               track-by="tax_name"
 | |
|               object
 | |
|             />
 | |
|           </BaseInputGroup>
 | |
| 
 | |
|           <BaseInputGroup
 | |
|             :label="$t('items.description')"
 | |
|             :content-loading="isFetchingInitialData"
 | |
|             :error="
 | |
|               v$.currentItem.description.$error &&
 | |
|               v$.currentItem.description.$errors[0].$message
 | |
|             "
 | |
|           >
 | |
|             <BaseTextarea
 | |
|               v-model="itemStore.currentItem.description"
 | |
|               :content-loading="isFetchingInitialData"
 | |
|               name="description"
 | |
|               :row="2"
 | |
|               rows="2"
 | |
|               @input="v$.currentItem.description.$touch()"
 | |
|             />
 | |
|           </BaseInputGroup>
 | |
| 
 | |
|           <div>
 | |
|             <BaseButton
 | |
|               :content-loading="isFetchingInitialData"
 | |
|               type="submit"
 | |
|               :loading="isSaving"
 | |
|             >
 | |
|               <template #left="slotProps">
 | |
|                 <BaseIcon
 | |
|                   v-if="!isSaving"
 | |
|                   name="SaveIcon"
 | |
|                   :class="slotProps.class"
 | |
|                 />
 | |
|               </template>
 | |
| 
 | |
|               {{ isEdit ? $t('items.update_item') : $t('items.save_item') }}
 | |
|             </BaseButton>
 | |
|           </div>
 | |
|         </BaseInputGrid>
 | |
|       </BaseCard>
 | |
|     </form>
 | |
|   </BasePage>
 | |
| </template>
 | |
| 
 | |
| <script setup>
 | |
| import { computed, ref } from 'vue'
 | |
| import { useRouter, useRoute } from 'vue-router'
 | |
| import { useI18n } from 'vue-i18n'
 | |
| import {
 | |
|   required,
 | |
|   minLength,
 | |
|   numeric,
 | |
|   minValue,
 | |
|   maxLength,
 | |
|   helpers,
 | |
| } from '@vuelidate/validators'
 | |
| import useVuelidate from '@vuelidate/core'
 | |
| import { useItemStore } from '@/scripts/stores/item'
 | |
| import { useCompanyStore } from '@/scripts/stores/company'
 | |
| import { useTaxTypeStore } from '@/scripts/stores/tax-type'
 | |
| import { useModalStore } from '@/scripts/stores/modal'
 | |
| import ItemUnitModal from '@/scripts/components/modal-components/ItemUnitModal.vue'
 | |
| import { useUserStore } from '@/scripts/stores/user'
 | |
| import abilities from '@/scripts/stub/abilities'
 | |
| 
 | |
| const itemStore = useItemStore()
 | |
| const taxTypeStore = useTaxTypeStore()
 | |
| const modalStore = useModalStore()
 | |
| const companyStore = useCompanyStore()
 | |
| const { t } = useI18n()
 | |
| const route = useRoute()
 | |
| const router = useRouter()
 | |
| const userStore = useUserStore()
 | |
| 
 | |
| const isSaving = ref(false)
 | |
| const taxPerItem = ref(companyStore.selectedCompanySettings.tax_per_item)
 | |
| 
 | |
| let isFetchingInitialData = ref(false)
 | |
| 
 | |
| itemStore.$reset()
 | |
| loadData()
 | |
| 
 | |
| const price = computed({
 | |
|   get: () => itemStore.currentItem.price / 100,
 | |
|   set: (value) => {
 | |
|     itemStore.currentItem.price = Math.round(value * 100)
 | |
|   },
 | |
| })
 | |
| 
 | |
| const taxes = computed({
 | |
|   get: () =>
 | |
|     itemStore?.currentItem?.taxes?.map((tax) => {
 | |
|       if (tax) {
 | |
|         return {
 | |
|           ...tax,
 | |
|           tax_type_id: tax.id,
 | |
|           tax_name: tax.name + ' (' + tax.percent + '%)',
 | |
|         }
 | |
|       }
 | |
|     }),
 | |
|   set: (value) => {
 | |
|     itemStore.currentItem.taxes = value
 | |
|   },
 | |
| })
 | |
| 
 | |
| const isEdit = computed(() => route.name === 'items.edit')
 | |
| 
 | |
| const pageTitle = computed(() =>
 | |
|   isEdit.value ? t('items.edit_item') : t('items.new_item')
 | |
| )
 | |
| 
 | |
| const getTaxTypes = computed(() => {
 | |
|   return taxTypeStore.taxTypes.map((tax) => {
 | |
|     return {
 | |
|       ...tax,
 | |
|       tax_type_id: tax.id,
 | |
|       tax_name: tax.name + ' (' + tax.percent + '%)',
 | |
|     }
 | |
|   })
 | |
| })
 | |
| 
 | |
| const isTaxPerItem = computed(() => taxPerItem.value === 'YES')
 | |
| 
 | |
| const rules = computed(() => {
 | |
|   return {
 | |
|     currentItem: {
 | |
|       name: {
 | |
|         required: helpers.withMessage(t('validation.required'), required),
 | |
|         minLength: helpers.withMessage(
 | |
|           t('validation.name_min_length', { count: 3 }),
 | |
|           minLength(3)
 | |
|         ),
 | |
|       },
 | |
| 
 | |
|       description: {
 | |
|         maxLength: helpers.withMessage(
 | |
|           t('validation.description_maxlength'),
 | |
|           maxLength(65000)
 | |
|         ),
 | |
|       },
 | |
|     },
 | |
|   }
 | |
| })
 | |
| 
 | |
| const v$ = useVuelidate(rules, itemStore)
 | |
| 
 | |
| async function addItemUnit() {
 | |
|   modalStore.openModal({
 | |
|     title: t('settings.customization.items.add_item_unit'),
 | |
|     componentName: 'ItemUnitModal',
 | |
|     size: 'sm',
 | |
|   })
 | |
| }
 | |
| 
 | |
| async function loadData() {
 | |
|   isFetchingInitialData.value = true
 | |
| 
 | |
|   await itemStore.fetchItemUnits({ limit: 'all' })
 | |
|   if (userStore.hasAbilities(abilities.VIEW_TAX_TYPE)) {
 | |
|     await taxTypeStore.fetchTaxTypes({ limit: 'all' })
 | |
|   }
 | |
| 
 | |
|   if (isEdit.value) {
 | |
|     let id = route.params.id
 | |
|     await itemStore.fetchItem(id)
 | |
|     itemStore.currentItem.tax_per_item === 1
 | |
|       ? (taxPerItem.value = 'YES')
 | |
|       : (taxPerItem.value = 'NO')
 | |
|   }
 | |
| 
 | |
|   isFetchingInitialData.value = false
 | |
| }
 | |
| 
 | |
| async function submitItem() {
 | |
|   v$.value.currentItem.$touch()
 | |
| 
 | |
|   if (v$.value.currentItem.$invalid) {
 | |
|     return false
 | |
|   }
 | |
| 
 | |
|   isSaving.value = true
 | |
| 
 | |
|   try {
 | |
|     let data = {
 | |
|       id: route.params.id,
 | |
|       ...itemStore.currentItem,
 | |
|     }
 | |
| 
 | |
|     if (itemStore.currentItem && itemStore.currentItem.taxes) {
 | |
|       data.taxes = itemStore.currentItem.taxes.map((tax) => {
 | |
|         return {
 | |
|           tax_type_id: tax.tax_type_id,
 | |
|           amount: price.value * tax.percent,
 | |
|           percent: tax.percent,
 | |
|           name: tax.name,
 | |
|           collective_tax: 0,
 | |
|         }
 | |
|       })
 | |
|     }
 | |
| 
 | |
|     const action = isEdit.value ? itemStore.updateItem : itemStore.addItem
 | |
| 
 | |
|     await action(data)
 | |
|     isSaving.value = false
 | |
|     router.push('/admin/items')
 | |
|     closeItemModal()
 | |
|   } catch (err) {
 | |
|     isSaving.value = false
 | |
|     return
 | |
|   }
 | |
|   function closeItemModal() {
 | |
|     modalStore.closeModal()
 | |
|     setTimeout(() => {
 | |
|       itemStore.resetCurrentItem()
 | |
|       modalStore.$reset()
 | |
|       v$.value.$reset()
 | |
|     }, 300)
 | |
|   }
 | |
| }
 | |
| </script>
 |