move frontend to upper dir.
This commit is contained in:
		
							
								
								
									
										19
									
								
								src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/App.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| <template> | ||||
|   <v-app> | ||||
|       <NavBar /> | ||||
|       <v-main> | ||||
|           <router-view /> | ||||
|       </v-main> | ||||
|   </v-app> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  import NavBar from './components/NavBar.vue'; | ||||
|  | ||||
|  export default { | ||||
|    name: 'App', | ||||
|    components: { | ||||
|      NavBar, | ||||
|    }, | ||||
|  } | ||||
| </script> | ||||
							
								
								
									
										1
									
								
								src/assets/icons/rectangle-xmark-solid.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/assets/icons/rectangle-xmark-solid.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/></svg> | ||||
| After Width: | Height: | Size: 576 B | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/assets/logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 46 KiB | 
							
								
								
									
										6
									
								
								src/assets/logo.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/assets/logo.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||||
| <path d="M261.126 140.65L164.624 307.732L256.001 466L377.028 256.5L498.001 47H315.192L261.126 140.65Z" fill="#1697F6"/> | ||||
| <path d="M135.027 256.5L141.365 267.518L231.64 111.178L268.731 47H256H14L135.027 256.5Z" fill="#AEDDFF"/> | ||||
| <path d="M315.191 47C360.935 197.446 256 466 256 466L164.624 307.732L315.191 47Z" fill="#1867C0"/> | ||||
| <path d="M268.731 47C76.0026 47 141.366 267.518 141.366 267.518L268.731 47Z" fill="#7BC6FF"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 526 B | 
							
								
								
									
										79
									
								
								src/components/AppFooter.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/components/AppFooter.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| <template> | ||||
|   <v-footer height="40" app> | ||||
|     <a | ||||
|       v-for="item in items" | ||||
|       :key="item.title" | ||||
|       :href="item.href" | ||||
|       :title="item.title" | ||||
|       class="d-inline-block mx-2 social-link" | ||||
|       rel="noopener noreferrer" | ||||
|       target="_blank" | ||||
|     > | ||||
|       <v-icon | ||||
|         :icon="item.icon" | ||||
|         :size="item.icon === '$vuetify' ? 24 : 16" | ||||
|       /> | ||||
|     </a> | ||||
|  | ||||
|     <div | ||||
|       class="text-caption text-disabled" | ||||
|       style="position: absolute; right: 16px;" | ||||
|     > | ||||
|       © 2016-{{ (new Date()).getFullYear() }} <span class="d-none d-sm-inline-block">Vuetify, LLC</span> | ||||
|       — | ||||
|       <a | ||||
|         class="text-decoration-none on-surface" | ||||
|         href="https://vuetifyjs.com/about/licensing/" | ||||
|         rel="noopener noreferrer" | ||||
|         target="_blank" | ||||
|       > | ||||
|         MIT License | ||||
|       </a> | ||||
|     </div> | ||||
|   </v-footer> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
|   const items = [ | ||||
|     { | ||||
|       title: 'Vuetify Documentation', | ||||
|       icon: `$vuetify`, | ||||
|       href: 'https://vuetifyjs.com/', | ||||
|     }, | ||||
|     { | ||||
|       title: 'Vuetify Support', | ||||
|       icon: 'mdi-shield-star-outline', | ||||
|       href: 'https://support.vuetifyjs.com/', | ||||
|     }, | ||||
|     { | ||||
|       title: 'Vuetify X', | ||||
|       icon: ['M2.04875 3.00002L9.77052 13.3248L1.99998 21.7192H3.74882L10.5519 14.3697L16.0486 21.7192H22L13.8437 10.8137L21.0765 3.00002H19.3277L13.0624 9.76874L8.0001 3.00002H2.04875ZM4.62054 4.28821H7.35461L19.4278 20.4308H16.6937L4.62054 4.28821Z'], | ||||
|       href: 'https://x.com/vuetifyjs', | ||||
|     }, | ||||
|     { | ||||
|       title: 'Vuetify GitHub', | ||||
|       icon: `mdi-github`, | ||||
|       href: 'https://github.com/vuetifyjs/vuetify', | ||||
|     }, | ||||
|     { | ||||
|       title: 'Vuetify Discord', | ||||
|       icon: ['M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z'], | ||||
|       href: 'https://community.vuetifyjs.com/', | ||||
|     }, | ||||
|     { | ||||
|       title: 'Vuetify Reddit', | ||||
|       icon: `mdi-reddit`, | ||||
|       href: 'https://reddit.com/r/vuetifyjs', | ||||
|     }, | ||||
|   ] | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="sass"> | ||||
|   .social-link :deep(.v-icon) | ||||
|     color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity)) | ||||
|     text-decoration: none | ||||
|     transition: .2s ease-in-out | ||||
|  | ||||
|     &:hover | ||||
|       color: rgba(25, 118, 210, 1) | ||||
| </style> | ||||
							
								
								
									
										58
									
								
								src/components/CasherModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/components/CasherModal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| <template> | ||||
|   <v-dialog v-model="dialog" max-width="400"> | ||||
|     <v-card> | ||||
|       <v-card-title>Calcular Devuelta</v-card-title> | ||||
|       <v-card-text> | ||||
|         <v-text-field | ||||
|           v-model.number="purchase" | ||||
|           label="Total de la compra" | ||||
|           type="number" | ||||
|           prefix="$" | ||||
|           readonly | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model.number="money" | ||||
|           label="Dinero" | ||||
|           type="number" | ||||
|           prefix="$" | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model.number="change_cash" | ||||
|           label="Devuelta" | ||||
|           type="number" | ||||
|           prefix="$" | ||||
|           readonly | ||||
|         ></v-text-field> | ||||
|       </v-card-text> | ||||
|       <v-card-actions> | ||||
|         <v-spacer></v-spacer> | ||||
|         <v-btn text @click="dialog = false">Cerrar</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   export default { | ||||
|     props: { | ||||
|       total_purchase: { | ||||
|         type: Number, | ||||
|         required: true | ||||
|       } | ||||
|     }, | ||||
|     data() { | ||||
|       return { | ||||
|         dialog: false, | ||||
|         money: null, | ||||
|       } | ||||
|     }, | ||||
|     computed: { | ||||
|       purchase() { | ||||
|         return this.total_purchase | ||||
|       }, | ||||
|       change_cash() { | ||||
|         return (this.money || 0) - this.total_purchase | ||||
|       }, | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										50
									
								
								src/components/CodeDialog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/components/CodeDialog.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| <template> | ||||
|   <v-dialog v-model="dialog" persistent> | ||||
|     <v-card> | ||||
|       <v-card-title> | ||||
|         Ingrese el código | ||||
|       </v-card-title> | ||||
|       <v-card-text> | ||||
|         <v-form id="code-form" @submit.prevent="verifyCode"> | ||||
|           <v-text-field v-model="code" label="Código" type="password" autocomplete="off" /> | ||||
|         </v-form> | ||||
|       </v-card-text> | ||||
|       <v-card-actions> | ||||
|         <v-btn type="submit" form="code-form">Aceptar</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   import { inject } from 'vue'; | ||||
|  | ||||
|   export default { | ||||
|     data() { | ||||
|       return { | ||||
|         api: inject('api'), | ||||
|         dialog: true, | ||||
|         code: '', | ||||
|       }; | ||||
|     }, | ||||
|     methods: { | ||||
|       verifyCode() { | ||||
|         this.api.isValidAdminCode(this.code) | ||||
|             .then(data => { | ||||
|               if (data['validCode']) { | ||||
|                 this.$emit('code-verified', true); | ||||
|                 this.dialog = false; | ||||
|               } else { | ||||
|                 alert('Código incorrecto'); | ||||
|                 this.$emit('code-verified', false); | ||||
|               } | ||||
|             }) | ||||
|             .catch(error => { | ||||
|               alert('Error al validar el código'); | ||||
|               this.$emit('code-verified', false); | ||||
|               console.error(error); | ||||
|             }); | ||||
|       } | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										99
									
								
								src/components/CreateCustomerModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/components/CreateCustomerModal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| <template> | ||||
|   <v-dialog v-model="showModal" max-width="600px"> | ||||
|     <v-card> | ||||
|       <v-card-title> | ||||
|         <span class="headline">Información del Cliente</span> | ||||
|       </v-card-title> | ||||
|       <v-card-text> | ||||
|         <v-form ref="form" v-model="valid"> | ||||
|           <v-text-field | ||||
|             v-model="customer.name" | ||||
|             label="Nombre" | ||||
|             :rules="[rules.required]" | ||||
|           ></v-text-field> | ||||
|           <v-text-field | ||||
|               v-model="customer.address" | ||||
|               label="Direccion" | ||||
|               :rules="[rules.required]" | ||||
|               required | ||||
|           ></v-text-field> | ||||
|           <v-text-field | ||||
|               v-model="customer.email" | ||||
|               label="Correo Electrónico" | ||||
|               :rules="[rules.required, rules.email]" | ||||
|               required | ||||
|           ></v-text-field> | ||||
|           <v-text-field | ||||
|             v-model="customer.phone" | ||||
|             label="Teléfono" | ||||
|             :rules="[rules.required]" | ||||
|             required | ||||
|           ></v-text-field> | ||||
|         </v-form> | ||||
|       </v-card-text> | ||||
|       <v-card-actions> | ||||
|         <v-spacer></v-spacer> | ||||
|         <v-btn color="blue darken-1" text @click="closeModal">Cancelar</v-btn> | ||||
|         <v-btn color="blue darken-1" text @click="submitForm">Guardar</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  export default { | ||||
|      data() { | ||||
|          return { | ||||
|              showModal: false, | ||||
|              api: inject('api'), | ||||
|              valid: false, | ||||
|              customer: { | ||||
|                  name: '', | ||||
|                  address: '', | ||||
|                  email: '', | ||||
|                  phone: '' | ||||
|              }, | ||||
|              rules: { | ||||
|                  required: value => !!value || 'Este campo es requerido.', | ||||
|                  email: value => { | ||||
|                      const pattern = /^[^ ]+@[^ ]+\.[a-z]{2,3}$/; | ||||
|                      return pattern.test(value) || 'El correo no es válido.'; | ||||
|                  } | ||||
|              } | ||||
|          }; | ||||
|      }, | ||||
|      methods: { | ||||
|          openModal() { | ||||
|              this.showModal = true; | ||||
|          }, | ||||
|          closeModal() { | ||||
|              this.showModal = false; | ||||
|              this.resetForm(); | ||||
|          }, | ||||
|          async submitForm() { | ||||
|              console.log(this.customer) | ||||
|              if (this.$refs.form.validate()) { | ||||
|                  this.api.createCustomer(this.customer) | ||||
|                      .then(data => { | ||||
|                        console.log('Cliente Guardado:', data); | ||||
|                        this.$emit('customerCreated', data); | ||||
|                        this.closeModal(); | ||||
|                      }) | ||||
|                      .catch(error => console.error('Error:', error)); | ||||
|              } | ||||
|          }, | ||||
|          resetForm() { | ||||
|              this.customer = { | ||||
|                  name: '', | ||||
|                  address: '', | ||||
|                  email: '', | ||||
|                  phone: '' | ||||
|              }; | ||||
|              this.$refs.form.reset(); | ||||
|          }, | ||||
|      } | ||||
|  }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| </style> | ||||
							
								
								
									
										26
									
								
								src/components/CurrencyText.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/components/CurrencyText.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| <template> | ||||
|   <span>{{ formattedValue }}</span> | ||||
| </template> | ||||
| <script> | ||||
|   export default { | ||||
|     props: { | ||||
|       value: { | ||||
|         type: Number, | ||||
|         required: true | ||||
|       }, | ||||
|       locale: { | ||||
|         type: String, | ||||
|         default: 'es-CO', | ||||
|       }, | ||||
|       currency: { | ||||
|         type: String, | ||||
|         default: 'COP', | ||||
|       }, | ||||
|     }, | ||||
|     computed: { | ||||
|       formattedValue() { | ||||
|         return new Intl.NumberFormat(this.locale, { style: 'currency', currency: this.currency }).format(this.value); | ||||
|       }, | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										157
									
								
								src/components/HelloWorld.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/components/HelloWorld.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | ||||
| <template> | ||||
|   <v-container class="fill-height"> | ||||
|     <v-responsive | ||||
|       class="align-centerfill-height mx-auto" | ||||
|       max-width="900" | ||||
|     > | ||||
|       <v-img | ||||
|         class="mb-4" | ||||
|         height="150" | ||||
|         src="@/assets/logo.png" | ||||
|       /> | ||||
|  | ||||
|       <div class="text-center"> | ||||
|         <div class="text-body-2 font-weight-light mb-n1">Welcome to</div> | ||||
|  | ||||
|         <h1 class="text-h2 font-weight-bold">Vuetify</h1> | ||||
|       </div> | ||||
|  | ||||
|       <div class="py-4" /> | ||||
|  | ||||
|       <v-row> | ||||
|         <v-col cols="12"> | ||||
|           <v-card | ||||
|             class="py-4" | ||||
|             color="surface-variant" | ||||
|             image="https://cdn.vuetifyjs.com/docs/images/one/create/feature.png" | ||||
|             prepend-icon="mdi-rocket-launch-outline" | ||||
|             rounded="lg" | ||||
|             variant="outlined" | ||||
|           > | ||||
|             <template #image> | ||||
|               <v-img position="top right" /> | ||||
|             </template> | ||||
|  | ||||
|             <template #title> | ||||
|               <h2 class="text-h5 font-weight-bold">Get started</h2> | ||||
|             </template> | ||||
|  | ||||
|             <template #subtitle> | ||||
|               <div class="text-subtitle-1"> | ||||
|                 Replace this page by removing <v-kbd>{{ `<HelloWorld />` }}</v-kbd> in <v-kbd>pages/index.vue</v-kbd>. | ||||
|               </div> | ||||
|             </template> | ||||
|  | ||||
|             <v-overlay | ||||
|               opacity=".12" | ||||
|               scrim="primary" | ||||
|               contained | ||||
|               model-value | ||||
|               persistent | ||||
|             /> | ||||
|           </v-card> | ||||
|         </v-col> | ||||
|  | ||||
|         <v-col cols="6"> | ||||
|           <v-card | ||||
|             append-icon="mdi-open-in-new" | ||||
|             class="py-4" | ||||
|             color="surface-variant" | ||||
|             href="https://vuetifyjs.com/" | ||||
|             prepend-icon="mdi-text-box-outline" | ||||
|             rel="noopener noreferrer" | ||||
|             rounded="lg" | ||||
|             subtitle="Learn about all things Vuetify in our documentation." | ||||
|             target="_blank" | ||||
|             title="Documentation" | ||||
|             variant="text" | ||||
|           > | ||||
|             <v-overlay | ||||
|               opacity=".06" | ||||
|               scrim="primary" | ||||
|               contained | ||||
|               model-value | ||||
|               persistent | ||||
|             /> | ||||
|           </v-card> | ||||
|         </v-col> | ||||
|  | ||||
|         <v-col cols="6"> | ||||
|           <v-card | ||||
|             append-icon="mdi-open-in-new" | ||||
|             class="py-4" | ||||
|             color="surface-variant" | ||||
|             href="https://vuetifyjs.com/introduction/why-vuetify/#feature-guides" | ||||
|             prepend-icon="mdi-star-circle-outline" | ||||
|             rel="noopener noreferrer" | ||||
|             rounded="lg" | ||||
|             subtitle="Explore available framework Features." | ||||
|             target="_blank" | ||||
|             title="Features" | ||||
|             variant="text" | ||||
|           > | ||||
|             <v-overlay | ||||
|               opacity=".06" | ||||
|               scrim="primary" | ||||
|               contained | ||||
|               model-value | ||||
|               persistent | ||||
|             /> | ||||
|           </v-card> | ||||
|         </v-col> | ||||
|  | ||||
|         <v-col cols="6"> | ||||
|           <v-card | ||||
|             append-icon="mdi-open-in-new" | ||||
|             class="py-4" | ||||
|             color="surface-variant" | ||||
|             href="https://vuetifyjs.com/components/all" | ||||
|             prepend-icon="mdi-widgets-outline" | ||||
|             rel="noopener noreferrer" | ||||
|             rounded="lg" | ||||
|             subtitle="Discover components in the API Explorer." | ||||
|             target="_blank" | ||||
|             title="Components" | ||||
|             variant="text" | ||||
|           > | ||||
|             <v-overlay | ||||
|               opacity=".06" | ||||
|               scrim="primary" | ||||
|               contained | ||||
|               model-value | ||||
|               persistent | ||||
|             /> | ||||
|           </v-card> | ||||
|         </v-col> | ||||
|  | ||||
|         <v-col cols="6"> | ||||
|           <v-card | ||||
|             append-icon="mdi-open-in-new" | ||||
|             class="py-4" | ||||
|             color="surface-variant" | ||||
|             href="https://discord.vuetifyjs.com" | ||||
|             prepend-icon="mdi-account-group-outline" | ||||
|             rel="noopener noreferrer" | ||||
|             rounded="lg" | ||||
|             subtitle="Connect with Vuetify developers." | ||||
|             target="_blank" | ||||
|             title="Community" | ||||
|             variant="text" | ||||
|           > | ||||
|             <v-overlay | ||||
|               opacity=".06" | ||||
|               scrim="primary" | ||||
|               contained | ||||
|               model-value | ||||
|               persistent | ||||
|             /> | ||||
|           </v-card> | ||||
|         </v-col> | ||||
|       </v-row> | ||||
|     </v-responsive> | ||||
|   </v-container> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
|   // | ||||
| </script> | ||||
							
								
								
									
										19
									
								
								src/components/ModalView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/components/ModalView.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| <template> | ||||
|   <div class="modal"> | ||||
|       <div class="head"> | ||||
|           <p>Nuevo Movimiento</p> | ||||
|           <img @click="close" src="@/assets/close-icon.svg" alt="cerrar"/> | ||||
|       </div> | ||||
|       <div class="body"> | ||||
|           <slot></slot> | ||||
|       </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
|  import { defineEmits } from "vue"; | ||||
|  | ||||
|  const emit = defineEmits(["close"]); | ||||
|  | ||||
|  const close = () => emit("close"); | ||||
| </script> | ||||
							
								
								
									
										47
									
								
								src/components/NavBar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/components/NavBar.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| <template> | ||||
|     <v-app-bar color="primary" prominent> | ||||
|     <v-app-bar-nav-icon variant="text" @click.stop="drawer = !drawer"></v-app-bar-nav-icon> | ||||
|     <v-toolbar-title>Menu</v-toolbar-title> | ||||
|     <v-spacer></v-spacer> | ||||
|     <template v-if="$vuetify.display.mdAndUp"> | ||||
|       <v-btn icon="mdi-magnify" variant="text"></v-btn> | ||||
|       <v-btn icon="mdi-filter" variant="text"></v-btn> | ||||
|     </template> | ||||
|     <v-btn icon="mdi-dots-vertical" variant="text"></v-btn> | ||||
|   </v-app-bar> | ||||
|   <v-navigation-drawer v-model="drawer" | ||||
|                        :location="$vuetify.display.mobile ? 'bottom' : undefined" | ||||
|                        temporary> | ||||
|       <v-list> | ||||
|           <v-list-item v-for="item in menuItems" :key="item.title" @click="navigate(item.route)"> | ||||
|               <v-list-item-title>{{ item.title }}</v-list-item-title> | ||||
|           </v-list-item> | ||||
|       </v-list> | ||||
|   </v-navigation-drawer> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|  export default { | ||||
|      name: 'NavBar', | ||||
|      data: () => ({ | ||||
|          drawer: false, | ||||
|          group: null, | ||||
|          menuItems: [ | ||||
|              { title: 'Inicio', route: '/'}, | ||||
|              { title: 'Comprar', route:'/comprar'}, | ||||
|              { title: 'Cuadrar tarro', route: '/cuadrar_tarro'}, | ||||
|              { title: 'Cuadres de tarro', route: '/cuadres_de_tarro'}, | ||||
|          ], | ||||
|      }), | ||||
|      watch: { | ||||
|          group () { | ||||
|              this.drawer = false | ||||
|          }, | ||||
|      }, | ||||
|      methods: { | ||||
|          navigate(route) { | ||||
|              this.$router.push(route); | ||||
|          }, | ||||
|      } | ||||
|  } | ||||
| </script> | ||||
							
								
								
									
										342
									
								
								src/components/Purchase.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								src/components/Purchase.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,342 @@ | ||||
| <template> | ||||
| <v-container> | ||||
|   <v-form ref="purchase" v-model="valid" @change="onFormChange"> | ||||
|     <v-row> | ||||
|         <v-col> | ||||
|             <v-autocomplete | ||||
|                 v-model="purchase.customer" | ||||
|                 :items="filteredClients" | ||||
|                 :search="client_search" | ||||
|                 no-data-text="No se hallaron clientes" | ||||
|                 item-title="name" | ||||
|                 item-value="id" | ||||
|                 @update:model-value="onFormChange" | ||||
|                 label="Cliente" | ||||
|                 :rules="[rules.required]" | ||||
|                 required | ||||
|                 class="mr-4" | ||||
|             ></v-autocomplete> | ||||
|             <v-btn color="primary" @click="openModal">Agregar Cliente</v-btn> | ||||
|             <CreateCustomerModal ref="customerModal" @customerCreated="handleNewCustomer"/> | ||||
|       </v-col> | ||||
|       <v-col lg="4"> | ||||
|           <v-text-field | ||||
|               v-model="purchase.date" | ||||
|               label="Fecha" | ||||
|               type="datetime-local" | ||||
|               :rules="[rules.required]" | ||||
|               required | ||||
|               readonly | ||||
|           ></v-text-field> | ||||
|       </v-col> | ||||
|      </v-row> | ||||
|     <v-textarea | ||||
|       v-model="purchase.notes" | ||||
|       label="Notas" | ||||
|       rows="2" | ||||
|       ></v-textarea> | ||||
|     <v-divider></v-divider> | ||||
|     <v-container> | ||||
|       <v-toolbar> | ||||
|         <v-toolbar-title secondary>Productos</v-toolbar-title> | ||||
|       </v-toolbar> | ||||
|       <v-container v-for="(line, index) in purchase.saleline_set" :key="line.id"> | ||||
|         <v-row> | ||||
|             <v-col | ||||
|                 lg="9"> | ||||
|             <v-autocomplete | ||||
|               v-model="line.product" | ||||
|               :items="filteredProducts" | ||||
|               :search="product_search" | ||||
|               @update:modelValue="onProductChange(index)" | ||||
|               no-data-text="No se hallaron productos" | ||||
|               item-title="name" | ||||
|               item-value="id" | ||||
|               item-subtitle="Price" | ||||
|               label="Producto" | ||||
|               :rules="[rules.required]" | ||||
|               required | ||||
|               > | ||||
|               <template v-slot:item="{ props, item }"> | ||||
|                 <v-list-item v-bind="props" :title="item.raw.name" :subtitle="formatPrice(item.raw.price)"></v-list-item> | ||||
|               </template> | ||||
|             </v-autocomplete> | ||||
|           </v-col> | ||||
|           <v-col | ||||
|               lg="2" | ||||
|           > | ||||
|               <v-text-field | ||||
|                   v-model.number="line.quantity" | ||||
|                   label="Cantidad" | ||||
|                   type="number" | ||||
|                   :rules="[rules.required,rules.positive]" | ||||
|                   required | ||||
|               ></v-text-field> | ||||
|           </v-col> | ||||
|         </v-row> | ||||
|         <v-row> | ||||
|           <v-col> | ||||
|             <v-text-field | ||||
|               v-model.number="line.unit_price" | ||||
|               label="Precio" | ||||
|               type="number" | ||||
|               :rules="[rules.required]" | ||||
|               prefix="$" | ||||
|               required | ||||
|               readonly | ||||
|               ></v-text-field> | ||||
|           </v-col> | ||||
|           <v-col> | ||||
|             <v-text-field | ||||
|               v-model="line.measuring_unit" | ||||
|               label="UdM" | ||||
|               persistent-placeholder="true" | ||||
|               readonly | ||||
|               ></v-text-field> | ||||
|           </v-col> | ||||
|           <v-col> | ||||
|             <v-text-field | ||||
|               type="number" | ||||
|               :value="calculateSubtotal(line)" | ||||
|               label="Subtotal" | ||||
|               prefix="$" | ||||
|               readonly | ||||
|                 disable | ||||
|                 persistent-placeholder="true" | ||||
|               ></v-text-field> | ||||
|             </v-col> | ||||
|             <v-col> | ||||
|               <v-btn @click="removeLine(index)" color="red">Eliminar</v-btn> | ||||
|             </v-col> | ||||
|         </v-row> | ||||
|         <v-alert type="warning" :duration="2000" closable v-model="show_alert_lines"> | ||||
|           No se puede eliminar la única línea. | ||||
|         </v-alert> | ||||
|       </v-container> | ||||
|         <v-btn @click="addLine" color="blue">Agregar</v-btn> | ||||
|         </v-container> | ||||
|         <v-divider></v-divider> | ||||
|         <v-text-field | ||||
|           :value="calculateTotal" | ||||
|           label="Total" | ||||
|           prefix="$" | ||||
|           readonly | ||||
|           persistent-placeholder="true" | ||||
|         ></v-text-field> | ||||
|         <v-container v-if="calculateTotal > 0"> | ||||
|           <v-select | ||||
|             :items="payment_methods" | ||||
|             v-model="purchase.payment_method" | ||||
|             item-title="text" | ||||
|             item-value="value" | ||||
|             label="Pago en" | ||||
|             :rules="[rules.required]" | ||||
|             required | ||||
|           ></v-select> | ||||
|           <v-btn @click="openCasherModal" v-if="purchase.payment_method === 'CASH'">Calcular Devuelta</v-btn> | ||||
|           <CasherModal :total_purchase="calculateTotal" ref="casherModal"</CasherModal> | ||||
|         </v-container> | ||||
|         <v-btn @click="submit" color="green">Comprar</v-btn> | ||||
|         <v-alert type="error" :duration="2000" closable v-model="show_alert_purchase"> | ||||
|           Verifique los campos obligatorios. | ||||
|         </v-alert> | ||||
|   </v-form> | ||||
| </v-container> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   import CustomerForm from './CreateCustomerModal.vue'; | ||||
|   import CasherModal from './CasherModal.vue'; | ||||
|   import { inject } from 'vue'; | ||||
|  | ||||
|   export default { | ||||
|     name: 'DonConfiao', | ||||
|     components: { | ||||
|      CustomerForm, | ||||
|      CasherModal, | ||||
|    }, | ||||
|    props: { | ||||
|      msg: String | ||||
|    }, | ||||
|    data() { | ||||
|      return { | ||||
|        api: inject('api'), | ||||
|        valid: false, | ||||
|        form_changed: false, | ||||
|        show_alert_lines: false, | ||||
|        show_alert_purchase: false, | ||||
|        client_search: '', | ||||
|        product_search: '', | ||||
|        payment_methods: null, | ||||
|        purchase: { | ||||
|          date: this.getCurrentDate(), | ||||
|          customer: null, | ||||
|          notes: '', | ||||
|          payment_method: null, | ||||
|          saleline_set: [{product:'', unit_price: 0, quantity: 0, unit: ''}], | ||||
|        }, | ||||
|        rules: { | ||||
|            required: value => !!value || 'Requerido.', | ||||
|            positive: value => value > 0 || 'La cantidad debe ser mayor que 0.', | ||||
|        }, | ||||
|          menuItems: [ | ||||
|              { title: 'Inicio', route: '/'}, | ||||
|              { title: 'Compras', route:'/compras'}, | ||||
|          ], | ||||
|          clients: [], | ||||
|          products: [], | ||||
|      }; | ||||
|    }, | ||||
|      created() { | ||||
|      this.fetchClients(); | ||||
|      this.fetchProducts(); | ||||
|      this.fetchPaymentMethods(); | ||||
|    }, | ||||
|    watch: { | ||||
|      group () { | ||||
|        this.drawer = false | ||||
|      }, | ||||
|    }, | ||||
|    beforeMount() { | ||||
|       window.addEventListener('beforeunload', this.confirmLeave); | ||||
|    }, | ||||
|    beforeDestroy() { | ||||
|       window.removeEventListener('beforeunload', this.confirmLeave); | ||||
|    }, | ||||
|    computed: { | ||||
|      calculateTotal() { | ||||
|        return this.purchase.saleline_set.reduce((total, saleline) => { | ||||
|          return total + this.calculateSubtotal(saleline); | ||||
|        }, 0); | ||||
|      }, | ||||
|      filteredClients() { | ||||
|        return this.clients.filter(client => { | ||||
|          if (this.client_search === '') { | ||||
|            return []; | ||||
|          } else { | ||||
|            return client.name.toLowerCase().includes(this.client_search.toLowerCase()); | ||||
|          } | ||||
|        }); | ||||
|      }, | ||||
|      filteredProducts() { | ||||
|        return this.products.filter(product => { | ||||
|          if (this.product_search === '') { | ||||
|            return []; | ||||
|          } else { | ||||
|            return product.name.toLowerCase().includes(this.product_search.toLowerCase()); | ||||
|          } | ||||
|        }); | ||||
|      }, | ||||
|    }, | ||||
|    methods: { | ||||
|      openModal() { | ||||
|       this.$refs.customerModal.openModal(); | ||||
|      }, | ||||
|      onFormChange() { | ||||
|        this.form_changed = true; | ||||
|      }, | ||||
|      openCasherModal() { | ||||
|        this.$refs.casherModal.dialog = true | ||||
|      }, | ||||
|      confirmLeave(event) { | ||||
|        if (this.form_changed) { | ||||
|          const message = '¿seguro que quieres salir? Perderas la información diligenciada'; | ||||
|          event.preventDefault(); | ||||
|          event.returnValue = message; | ||||
|          return message; | ||||
|        } | ||||
|      }, | ||||
|      getCurrentDate() { | ||||
|          const today = new Date(); | ||||
|          const gmtOffSet = -5; | ||||
|          const localDate = new Date(today.getTime() + (gmtOffSet * 60 * 60 * 1000)); | ||||
|          // Formatear la fecha y hora en el formato YYYY-MM-DDTHH:MM | ||||
|          const formattedDate = localDate.toISOString().slice(0,16); | ||||
|          return formattedDate; | ||||
|      }, | ||||
|        onProductChange(index) { | ||||
|            const selectedProductId = this.purchase.saleline_set[index].product; | ||||
|        const selectedProduct = this.products.find(p => p.id == selectedProductId); | ||||
|          this.purchase.saleline_set[index].unit_price = selectedProduct.price; | ||||
|          this.purchase.saleline_set[index].measuring_unit = selectedProduct.measuring_unit; | ||||
|      }, | ||||
|      fetchClients() { | ||||
|        this.api.getCustomers() | ||||
|            .then(data => { | ||||
|              this.clients = data; | ||||
|            }) | ||||
|            .catch(error => { | ||||
|              console.error(error); | ||||
|            }); | ||||
|      }, | ||||
|        handleNewCustomer(newCustomer){ | ||||
|            this.clients.push(newCustomer); | ||||
|            this.purchase.customer = newCustomer.id; | ||||
|        }, | ||||
|      fetchProducts() { | ||||
|        this.api.getProducts() | ||||
|            .then(data => { | ||||
|              this.products = data; | ||||
|            }) | ||||
|            .catch(error => { | ||||
|              console.error(error); | ||||
|            }); | ||||
|      }, | ||||
|      fetchPaymentMethods() { | ||||
|        this.api.getPaymentMethods() | ||||
|            .then(data => { | ||||
|              this.payment_methods = data; | ||||
|            }) | ||||
|            .catch(error => { | ||||
|              console.error(error); | ||||
|            }); | ||||
|      }, | ||||
|      addLine() { | ||||
|        this.purchase.saleline_set.push({ product: '', unit_price: 0, quantity:0, measuring_unit: ''}); | ||||
|      }, | ||||
|      removeLine(index) { | ||||
|          if (this.purchase.saleline_set.length > 1) { | ||||
|              this.purchase.saleline_set.splice(index, 1); | ||||
|          } else { | ||||
|            this.show_alert_lines = true; | ||||
|            setTimeout(() => { | ||||
|              this.show_alert_lines = false; | ||||
|            }, 2000); | ||||
|          } | ||||
|      }, | ||||
|      calculateSubtotal(line) { | ||||
|        return line.unit_price * line.quantity; | ||||
|      }, | ||||
|      async submit() { | ||||
|        this.$refs.purchase.validate(); | ||||
|          if (this.valid) { | ||||
|            this.api.createPurchase(this.purchase) | ||||
|                .then(data => { | ||||
|                  console.log('Compra enviada:', data); | ||||
|                  this.$router.push({ | ||||
|                    path: "/summary_purchase", | ||||
|                    query : {id: parseInt(data.id)} | ||||
|                  }); | ||||
|                }) | ||||
|            .catch(error => console.error('Error al enviarl la compra:', error)); | ||||
|        } else { | ||||
|          this.show_alert_purchase = true; | ||||
|            setTimeout(() => { | ||||
|              this.show_alert_purchase = false; | ||||
|            }, 4000); | ||||
|        } | ||||
|      }, | ||||
|      navigate(route) { | ||||
|        this.$router.push(route); | ||||
|      }, | ||||
|      formatPrice(price) { | ||||
|        return new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'COP' }).format(price); | ||||
|      }, | ||||
|    }, | ||||
|      mounted() { | ||||
|          this.fetchClients(); | ||||
|      } | ||||
|  }; | ||||
| </script> | ||||
| <style> | ||||
| </style> | ||||
							
								
								
									
										35
									
								
								src/components/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/components/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| # Components | ||||
|  | ||||
| Vue template files in this folder are automatically imported. | ||||
|  | ||||
| ## 🚀 Usage | ||||
|  | ||||
| Importing is handled by [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components). This plugin automatically imports `.vue` files created in the `src/components` directory, and registers them as global components. This means that you can use any component in your application without having to manually import it. | ||||
|  | ||||
| The following example assumes a component located at `src/components/MyComponent.vue`: | ||||
|  | ||||
| ```vue | ||||
| <template> | ||||
|   <div> | ||||
|     <MyComponent /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   // | ||||
| </script> | ||||
| ``` | ||||
|  | ||||
| When your template is rendered, the component's import will automatically be inlined, which renders to this: | ||||
|  | ||||
| ```vue | ||||
| <template> | ||||
|   <div> | ||||
|     <MyComponent /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   import MyComponent from '@/components/MyComponent.vue' | ||||
| </script> | ||||
| ``` | ||||
							
								
								
									
										200
									
								
								src/components/ReconciliationJar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/components/ReconciliationJar.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | ||||
| <template> | ||||
|   <v-container> | ||||
|     <v-toolbar> | ||||
|       <v-toolbar-title> Cuadre del Tarro </v-toolbar-title> | ||||
|     </v-toolbar> | ||||
|     <v-card> | ||||
|       <v-card-text> | ||||
|         <v-form ref="taker" v-model="valid"> | ||||
|           <v-text-field | ||||
|             v-model="reconciliation.date_time" | ||||
|             label="Fecha" | ||||
|             type="datetime-local" | ||||
|             :rules="[rules.required]" | ||||
|             required | ||||
|             readonly | ||||
|           ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.reconcilier" | ||||
|           label="Cajero" | ||||
|           :rules="[rules.required]" | ||||
|           required | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.total_cash_purchases" | ||||
|           label="Total Ventas en efectivo" | ||||
|           :rules="[rules.required]" | ||||
|           prefix="$" | ||||
|           type="number" | ||||
|           readonly | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.cash_taken" | ||||
|           label="Dinero Recogido" | ||||
|           :rules="[rules.required]" | ||||
|           prefix="$" | ||||
|           type="number" | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.cash_discrepancy" | ||||
|           label="Descuadre" | ||||
|           :rules="[rules.integer]" | ||||
|           prefix="$" | ||||
|           type="number" | ||||
|         ></v-text-field> | ||||
|         <v-btn @click="submit" color="green">Recoger Dinero</v-btn> | ||||
|         </v-form> | ||||
|       </v-card-text> | ||||
|     </v-card> | ||||
|     <v-tabs v-model="selectedTab"> | ||||
|       <v-tab | ||||
|         v-for="(purchases, payment_method) in summary.purchases" | ||||
|         :key="payment_method" | ||||
|         :value="payment_method" | ||||
|       > | ||||
|         {{ payment_method }}  <CurrencyText :value="totalByMethod(payment_method)"</CurrencyText> | ||||
|       </v-tab> | ||||
|     </v-tabs> | ||||
|  | ||||
|     <v-tabs-window v-model="selectedTab"> | ||||
|       <v-card> | ||||
|         <v-card-text> | ||||
|           <v-tabs-window-item | ||||
|             v-for="(purchases, payment_method) in summary.purchases" | ||||
|             :key="payment_method" | ||||
|             :value="payment_method" | ||||
|           > | ||||
|             <v-data-table-virtual | ||||
|               :headers="summary.headers" | ||||
|               :items="summary.purchases[payment_method]" | ||||
|             > | ||||
|               <template v-slot:item.id="{ item }"> | ||||
|                 <v-btn @click="openSummaryModal(item.id)">{{ item.id }}</v-btn> | ||||
|               </template> | ||||
|               <template v-slot:item.total="{ item }"> | ||||
|                 <CurrencyText :value="parseFloat(item.total)"></CurrencyText> | ||||
|               </template> | ||||
|             </v-data-table-virtual> | ||||
|           </v-tabs-window-item> | ||||
|           <SummaryPurchaseModal :id="selectedPurchaseId" ref="summaryModal" /> | ||||
|         </v-card-text> | ||||
|       </v-card> | ||||
|     </v-tabs-window> | ||||
|   </v-container> | ||||
| </template> | ||||
| <script> | ||||
|   import { inject } from 'vue'; | ||||
|   import CurrencyText from './CurrencyText.vue'; | ||||
|   import SummaryPurchaseModal from './SummaryPurchaseModal.vue'; | ||||
|  | ||||
|   export default { | ||||
|     name: 'ReconciliationJar', | ||||
|     props: { | ||||
|       msg: String, | ||||
|     }, | ||||
|     components: { | ||||
|       SummaryPurchaseModal, | ||||
|     }, | ||||
|     data () { | ||||
|       return { | ||||
|         api: inject('api'), | ||||
|         valid: null, | ||||
|         selectedPurchaseId: null, | ||||
|         selectedTab: 'CASH', | ||||
|         reconciliation: { | ||||
|           date_time: '', | ||||
|           total_cash_purchases: 0, | ||||
|           cash_taken: 0, | ||||
|           cash_discrepancy: 0, | ||||
|           other_totals: { | ||||
|           }, | ||||
|           cash_purchases: [], | ||||
|         }, | ||||
|         summary: { | ||||
|           headers: [ | ||||
|             {title: 'Id', value: 'id'}, | ||||
|             {title: 'Fecha', value: 'date'}, | ||||
|             {title: 'Cliente', value: 'customer.name'}, | ||||
|             {title: 'Total', value: 'total'}, | ||||
|           ], | ||||
|           purchases: {}, | ||||
|         }, | ||||
|         rules: { | ||||
|           required: value => !!value || 'Requerido.', | ||||
|           integer: value => !!value || value === 0 || 'Requerido.', | ||||
|         }, | ||||
|       }; | ||||
|     }, | ||||
|     mounted() { | ||||
|       this.fetchPurchases(); | ||||
|       this.reconciliation.date_time = this.getCurrentDate(); | ||||
|     }, | ||||
|     watch: { | ||||
|       'reconciliation.cash_taken'() { | ||||
|         this.updateDiscrepancy(); | ||||
|       }, | ||||
|     }, | ||||
|     methods: { | ||||
|       totalByMethod(method) { | ||||
|         if (method in this.summary.purchases) { | ||||
|           return this.summary.purchases[method].reduce((a, b) => a + parseFloat(b.total), 0); | ||||
|         } | ||||
|         return 0; | ||||
|       }, | ||||
|       idsBymethod(method) { | ||||
|         if (method in this.summary.purchases) { | ||||
|           return this.summary.purchases[method].map(purchase => purchase.id) | ||||
|         } | ||||
|         return []; | ||||
|       }, | ||||
|       processOtherMethods() { | ||||
|         for (const method of Object.keys(this.summary.purchases)) { | ||||
|           if (method !== 'CASH') { | ||||
|             this.reconciliation.other_totals[method] = { | ||||
|               total: this.totalByMethod(method), | ||||
|               purchases: this.idsBymethod(method), | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       updateDiscrepancy() { | ||||
|         this.reconciliation.cash_discrepancy = (this.reconciliation.total_cash_purchases || 0 ) - (this.reconciliation.cash_taken || 0); | ||||
|       }, | ||||
|       getCurrentDate() { | ||||
|         const today = new Date(); | ||||
|         const gmtOffSet = -5; | ||||
|         const localDate = new Date(today.getTime() + (gmtOffSet * 60 * 60 * 1000)); | ||||
|         // Formatear la fecha y hora en el formato YYYY-MM-DDTHH:MM | ||||
|         const formattedDate = localDate.toISOString().slice(0,16); | ||||
|         return formattedDate; | ||||
|       }, | ||||
|       openSummaryModal(id) { | ||||
|         this.selectedPurchaseId = id; | ||||
|         this.$refs.summaryModal.dialog = true; | ||||
|       }, | ||||
|       fetchPurchases() { | ||||
|         this.api.getPurchasesForReconciliation() | ||||
|             .then(data => { | ||||
|               this.summary.purchases = data; | ||||
|               this.reconciliation.cash_purchases = this.idsBymethod('CASH'); | ||||
|               this.reconciliation.total_cash_purchases = this.totalByMethod('CASH'); | ||||
|               this.processOtherMethods(); | ||||
|             }) | ||||
|             .catch(error => { | ||||
|               console.error(error); | ||||
|             }); | ||||
|       }, | ||||
|       async submit() { | ||||
|         this.$refs.taker.validate(); | ||||
|         if (this.valid) { | ||||
|           this.api.createReconciliationJar(this.reconciliation) | ||||
|               .then(data => { | ||||
|                 console.log('Cuadre enviado:', data); | ||||
|                 this.$router.push({path: "/"}); | ||||
|               }) | ||||
|               .catch(error => console.error('Error:', error)); | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										64
									
								
								src/components/ReconciliationJarIndex.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/components/ReconciliationJarIndex.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| <template> | ||||
|   <v-container> | ||||
|     <v-toolbar> | ||||
|       <v-toolbar-title> Cuadres del Tarro </v-toolbar-title> | ||||
|     </v-toolbar> | ||||
|     <v-card> | ||||
|       <v-card-text> | ||||
|         <v-data-table-server | ||||
|           v-model:items-per-page="itemsPerPage" | ||||
|           :headers="headers" | ||||
|           :items="serverItems" | ||||
|           :items-length="totalItems" | ||||
|           :loading="loading" | ||||
|           :search="search" | ||||
|           @update:options="loadItems" | ||||
|         > | ||||
|           <template v-slot:item.id="{ item }"> | ||||
|             <v-btn @click="openSummaryModal(item.id)">{{ item.id }}</v-btn> | ||||
|           </template> | ||||
|         </v-data-table-server> | ||||
|         <SummaryReconciliationModal :id="selectedReconciliationId" ref="summaryModal" /> | ||||
|       </v-card-text> | ||||
|     </v-card> | ||||
|   </v-container> | ||||
| </template> | ||||
| <script> | ||||
|   export default { | ||||
|     data() { | ||||
|       return { | ||||
|         api: inject('api'), | ||||
|         selectedReconciliationId: null, | ||||
|         itemsPerPage: 10, | ||||
|         headers: [ | ||||
|           { title: 'Acciones', key: 'id'}, | ||||
|           { title: 'Fecha', key: 'date_time'}, | ||||
|           { title: 'Reconciliador', key: 'reconcilier'}, | ||||
|           { title: 'Total Compras Efectivo', key: 'total_cash_purchases'}, | ||||
|           { title: 'Recogido', key: 'cash_taken'}, | ||||
|           { title: 'Descuadre', key: 'cash_discrepancy'}, | ||||
|         ], | ||||
|         search: '', | ||||
|         serverItems: [], | ||||
|         loading: true, | ||||
|         totalItems: 0, | ||||
|       } | ||||
|     }, | ||||
|     methods: { | ||||
|       loadItems ({page, itemsPerPage}) { | ||||
|         this.loading = true; | ||||
|         this.api.getListReconcliations(page, itemsPerPage) | ||||
|             .then(data => { | ||||
|               this.serverItems = data['results']; | ||||
|               this.totalItems = data['count']; | ||||
|               this.loading = false; | ||||
|             }) | ||||
|             .catch(error => console.log('Error:', error)); | ||||
|       }, | ||||
|       openSummaryModal(id) { | ||||
|         this.selectedReconciliationId = id.toString(); | ||||
|         this.$refs.summaryModal.dialog = true; | ||||
|       }, | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										140
									
								
								src/components/ReconciliationJarView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/components/ReconciliationJarView.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| <template> | ||||
|   <v-container> | ||||
|     <v-toolbar> | ||||
|       <v-toolbar-title> Cuadre de Tarro: {{ id }}</v-toolbar-title> | ||||
|     </v-toolbar> | ||||
|     <v-card> | ||||
|       <v-card-text> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.date_time" | ||||
|           label="Fecha" | ||||
|           required | ||||
|           readonly | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.reconcilier" | ||||
|           label="Cajero" | ||||
|           required | ||||
|           readonly | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.total_cash_purchases" | ||||
|           label="Total Ventas en efectivo" | ||||
|           prefix="$" | ||||
|           type="number" | ||||
|           readonly | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.cash_taken" | ||||
|           label="Dinero Recogido" | ||||
|           prefix="$" | ||||
|           type="number" | ||||
|         ></v-text-field> | ||||
|         <v-text-field | ||||
|           v-model="reconciliation.cash_discrepancy" | ||||
|           label="Descuadre" | ||||
|           prefix="$" | ||||
|           type="number" | ||||
|         ></v-text-field> | ||||
|         <v-tabs v-model="tab"> | ||||
|           <v-tab | ||||
|             v-for="(elements, paymentMethod) in purchases" | ||||
|             :key="paymentMethod" | ||||
|           > | ||||
|             {{ paymentMethod }}  <CurrencyText :value="elements.total"</CurrencyText> | ||||
|           </v-tab> | ||||
|         </v-tabs> | ||||
|         <v-tabs-window v-model="tab"> | ||||
|           <v-tabs-window-item | ||||
|             v-for="(elements, paymentMethod) in purchases" | ||||
|             :key="paymentMethod" | ||||
|           > | ||||
|             <v-table> | ||||
|               <thead> | ||||
|                 <tr> | ||||
|                   <th>Id</th> | ||||
|                   <th>Fecha</th> | ||||
|                   <th>Cliente</th> | ||||
|                   <th>Total</th> | ||||
|                 </tr> | ||||
|               </thead> | ||||
|               <tbody> | ||||
|                 <tr v-for="purchase in elements.purchases" :key="purchase.id"> | ||||
|                   <td><v-btn @click="openSummaryModal(purchase.id)">{{ purchase.id }}</v-btn></td> | ||||
|                   <td>{{ purchase.date }}</td> | ||||
|                   <td>{{ purchase.customer }}</td> | ||||
|                   <td><CurrencyText :value="purchase.total"</CurrencyText></td> | ||||
|  | ||||
|                 </tr> | ||||
|               </tbody> | ||||
|             </v-table> | ||||
|  | ||||
|           </v-tabs-window-item> | ||||
|         </v-tabs-window> | ||||
|         <SummaryPurchaseModal :id="selectedPurchaseId" ref="summaryModal" /> | ||||
|       </v-card-text> | ||||
|     </v-card> | ||||
|   </v-container> | ||||
| </template> | ||||
| <script> | ||||
|   import { inject } from 'vue'; | ||||
|  | ||||
|   export default { | ||||
|     name: 'ReconciliationJar View', | ||||
|     props: { | ||||
|       msg: String, | ||||
|       id: { | ||||
|         type: String, | ||||
|         required: true | ||||
|       } | ||||
|     }, | ||||
|     data () { | ||||
|       return { | ||||
|         tab: '0', | ||||
|         selectedPurchaseId: null, | ||||
|         api: inject('api'), | ||||
|         valid: null, | ||||
|         reconciliation: { | ||||
|         }, | ||||
|         purchases: {}, | ||||
|       }; | ||||
|     }, | ||||
|     created() { | ||||
|       if (this.id) { | ||||
|         this.fetchReconciliation(this.id); | ||||
|       } else { | ||||
|         console.error('No se proporcionó ID'); | ||||
|       } | ||||
|     }, | ||||
|     methods: { | ||||
|       fetchReconciliation(reconciliationId) { | ||||
|         this.api.getReconciliation(reconciliationId) | ||||
|             .then(data => { | ||||
|               this.reconciliation = data; | ||||
|               this.groupPurchases(); | ||||
|             }) | ||||
|             .catch(error => console.error(error)); | ||||
|       }, | ||||
|       groupPurchases() { | ||||
|         if (this.reconciliation.Sales) { | ||||
|           this.purchases = this.reconciliation.Sales.reduce((grouped, sale) => { | ||||
|             const paymentMethod = sale.payment_method; | ||||
|             if (!grouped[paymentMethod]) { | ||||
|               grouped[paymentMethod] = { | ||||
|                 purchases: [], | ||||
|                 total: 0, | ||||
|               }; | ||||
|             } | ||||
|             grouped[paymentMethod].purchases.push(sale); | ||||
|             grouped[paymentMethod].total += sale.total; | ||||
|             return grouped; | ||||
|           }, {}); | ||||
|         } | ||||
|       }, | ||||
|       openSummaryModal(id) { | ||||
|         this.selectedPurchaseId = id; | ||||
|         this.$refs.summaryModal.dialog = true; | ||||
|       }, | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										5
									
								
								src/components/ResaltedText.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/components/ResaltedText.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <template> | ||||
|   <strong class="text-red-darken-4"> | ||||
|     <slot></slot> | ||||
|   </strong> | ||||
| </template> | ||||
							
								
								
									
										107
									
								
								src/components/SummaryPurchase.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/components/SummaryPurchase.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| <template> | ||||
|  | ||||
|   <v-container> | ||||
|     <v-container v-show="!id"> | ||||
|       <v-toolbar> | ||||
|         <v-toolbar-title> No se indicó Id de la compra</v-toolbar-title> | ||||
|       </v-toolbar> | ||||
|     </v-container> | ||||
|     <v-container v-show="id"> | ||||
|       <v-toolbar> | ||||
|         <v-toolbar-title> Resumen de la compra {{ id }}</v-toolbar-title> | ||||
|       </v-toolbar> | ||||
|       <v-list> | ||||
|         <v-list-item> | ||||
|             <v-list-item-title>Fecha:</v-list-item-title> | ||||
|             <v-list-item-subtitle>{{ purchase.date }}</v-list-item-subtitle> | ||||
|         </v-list-item> | ||||
|         <v-list-item> | ||||
|             <v-list-item-title>Cliente:</v-list-item-title> | ||||
|             <v-list-item-subtitle v-if="purchase.customer">{{ purchase.customer.name }}</v-list-item-subtitle> | ||||
|         </v-list-item> | ||||
|         <v-list-item> | ||||
|             <v-list-item-title>Pagado en:</v-list-item-title> | ||||
|             <v-list-item-subtitle v-if="purchase.payment_method">{{ purchase.payment_method }}</v-list-item-subtitle> | ||||
|         </v-list-item> | ||||
|         <v-list-item> | ||||
|             <v-list-item-title>Total:</v-list-item-title> | ||||
|             <v-list-item-subtitle v-if="purchase.lines">{{ currencyFormat(calculateTotal(purchase.lines)) }}</v-list-item-subtitle> | ||||
|         </v-list-item> | ||||
|       </v-list> | ||||
|       <v-data-table-virtual | ||||
|         :headers="headers" | ||||
|         :items="purchase.lines" | ||||
|       > | ||||
|         <template v-slot:item.unit_price="{ item }"> | ||||
|           {{ currencyFormat(item.unit_price) }} | ||||
|         </template> | ||||
|         <template v-slot:item.subtotal="{ item }"> | ||||
|           {{ currencyFormat(calculateSubtotal(item.unit_price, item.quantity)) }} | ||||
|         </template> | ||||
|       </v-data-table-virtual> | ||||
|       <div class="text-center"> | ||||
|         <v-btn :to="{ path: 'comprar' }" color="green">Ir a Comprar</v-btn> | ||||
|       </div> | ||||
|     </v-container> | ||||
|   </v-container> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   import { inject } from 'vue'; | ||||
|  | ||||
|   export default { | ||||
|     name: 'SummaryPurchase', | ||||
|     props: { | ||||
|       msg: String, | ||||
|       id: Number | ||||
|     }, | ||||
|     data () { | ||||
|       return { | ||||
|         api: inject('api'), | ||||
|         purchase: {}, | ||||
|         headers: [ | ||||
|           { title: 'Producto', value: 'product.name' }, | ||||
|           { title: 'Precio', value: 'unit_price' }, | ||||
|           { title: 'Cantidad', value: 'quantity' }, | ||||
|           { title: 'Subtotal', value: 'subtotal' }, | ||||
|         ], | ||||
|       }; | ||||
|     }, | ||||
|     created() { | ||||
|       if (this.id) { | ||||
|         this.fetchPurchase(this.id); | ||||
|       } else { | ||||
|         console.error('No se proporcionó un ID de compra.'); | ||||
|       } | ||||
|     }, | ||||
|     methods: { | ||||
|       fetchPurchase(purchaseId) { | ||||
|         this.api.getSummaryPurchase(purchaseId) | ||||
|           .then(data => { | ||||
|             this.purchase = data; | ||||
|           }) | ||||
|           .catch(error => { | ||||
|             console.error(error); | ||||
|           }); | ||||
|       }, | ||||
|       currencyFormat(value) { | ||||
|         return new Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP' }).format(value); | ||||
|       }, | ||||
|       calculateSubtotal(price, quantity) { | ||||
|         price = parseFloat(price || 0); | ||||
|         quantity = parseFloat(quantity || 0); | ||||
|         return price * quantity; | ||||
|       }, | ||||
|       calculateTotal(lines) { | ||||
|         let total = 0; | ||||
|         lines.forEach(line => { | ||||
|           total += this.calculateSubtotal(line.unit_price, line.quantity); | ||||
|         }); | ||||
|         return total; | ||||
|       } | ||||
|     }, | ||||
|   }; | ||||
| </script> | ||||
| <style> | ||||
|  | ||||
| </style> | ||||
							
								
								
									
										30
									
								
								src/components/SummaryPurchaseModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/components/SummaryPurchaseModal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| <template> | ||||
|   <v-dialog v-model="dialog" max-width="400"> | ||||
|     <v-card> | ||||
|       <v-card-text> | ||||
|         <SummaryPurchase :id="id"/> | ||||
|       </v-card-text> | ||||
|       <v-card-actions> | ||||
|         <v-spacer></v-spacer> | ||||
|         <v-btn text @click="dialog = false">Cerrar</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   export default { | ||||
|     name: 'SummaryPurchase Modal', | ||||
|     props: { | ||||
|       id: { | ||||
|         type: Number, | ||||
|         required: true, | ||||
|       } | ||||
|     }, | ||||
|     data() { | ||||
|       return { | ||||
|         dialog: false, | ||||
|       } | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										30
									
								
								src/components/SummaryReconciliation.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/components/SummaryReconciliation.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| <template> | ||||
|   <v-dialog v-model="dialog" max-width="400"> | ||||
|     <v-card> | ||||
|       <v-card-text> | ||||
|         <SummaryPurchase :id="id"/> | ||||
|       </v-card-text> | ||||
|       <v-card-actions> | ||||
|         <v-spacer></v-spacer> | ||||
|         <v-btn text @click="dialog = false">Cerrar</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   export default { | ||||
|     name: 'SummaryPurchase Modal', | ||||
|     props: { | ||||
|       id: { | ||||
|         type: Number, | ||||
|         required: true, | ||||
|       } | ||||
|     }, | ||||
|     data() { | ||||
|       return { | ||||
|         dialog: false, | ||||
|       } | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										31
									
								
								src/components/SummaryReconciliationModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/components/SummaryReconciliationModal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| <template> | ||||
|   <v-dialog v-model="dialog" max-width="400"> | ||||
|     resumen | ||||
|     <v-card> | ||||
|       <v-card-text> | ||||
|         <ReconciliationJarView :id="id"/> | ||||
|       </v-card-text> | ||||
|       <v-card-actions> | ||||
|         <v-spacer></v-spacer> | ||||
|         <v-btn text @click="dialog = false">Cerrar</v-btn> | ||||
|       </v-card-actions> | ||||
|     </v-card> | ||||
|   </v-dialog> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   export default { | ||||
|     name: 'Summary Reconciliation Modal', | ||||
|     props: { | ||||
|       id: { | ||||
|         type: String, | ||||
|         required: true, | ||||
|       } | ||||
|     }, | ||||
|     data() { | ||||
|       return { | ||||
|         dialog: false, | ||||
|       } | ||||
|     }, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										34
									
								
								src/components/Wellcome.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/components/Wellcome.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| <template> | ||||
|   <v-container > | ||||
|     <v-responsive> | ||||
|       <v-toolbar> | ||||
|         <v-toolbar-title>Don Confiao te atiende</v-toolbar-title> | ||||
|       </v-toolbar> | ||||
|       <v-card> | ||||
|         <v-card-title>Hacer parte de la tienda la ilusión</v-card-title> | ||||
|         <v-card-text> | ||||
|           Recuerda que participando de esta tienda le apuestas a la economía solidaria, al mercado justo, a la alimentación sana, al campesinado colombiano y a un mundo mejor. | ||||
|         </v-card-text> | ||||
|       </v-card> | ||||
|  | ||||
|       <v-card> | ||||
|         <v-card-title>En desarrollo</v-card-title> | ||||
|         <v-card-text> | ||||
|           Don confiao apenas esta entendiendo como funciona esta tienda y por ahora <ResaltedText>solo puede atender las compras de contado</ResaltedText>, ya sea en efectivo o consignación. | ||||
|  | ||||
|           <v-alert type="warning"> | ||||
|             Si no vas a pagar tu compra recuerda que debes hacerlo en la planilla manual</v-alert> | ||||
|         </v-card-text> | ||||
|       </v-card> | ||||
|       <v-card> | ||||
|         <v-card-title>A comprar</v-card-title> | ||||
|         <v-card-text> | ||||
|           El siguiente botón te permitirá registrar tu compra. Cuando finalices te pedimos que ingrese el número de la compra, la fecha y el valor en la planilla física. | ||||
|           <div class="text-center"> | ||||
|             <v-btn :to="{ path: 'comprar' }" color="green">Ir a Comprar</v-btn> | ||||
|           </div> | ||||
|         </v-card-text> | ||||
|       </v-card> | ||||
|     </v-responsive> | ||||
|   </v-container> | ||||
| </template> | ||||
							
								
								
									
										5
									
								
								src/layouts/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/layouts/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| # Layouts | ||||
|  | ||||
| Layouts are reusable components that wrap around pages. They are used to provide a consistent look and feel across multiple pages. | ||||
|  | ||||
| Full documentation for this feature can be found in the Official [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) repository. | ||||
							
								
								
									
										13
									
								
								src/layouts/default.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/layouts/default.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <template> | ||||
|   <v-app> | ||||
|     <v-main> | ||||
|       <router-view /> | ||||
|     </v-main> | ||||
|  | ||||
|     <AppFooter /> | ||||
|   </v-app> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
|   // | ||||
| </script> | ||||
							
								
								
									
										26
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/main.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| /** | ||||
|  * main.js | ||||
|  * | ||||
|  * Bootstraps Vuetify and other plugins then mounts the App` | ||||
|  */ | ||||
|  | ||||
| // Plugins | ||||
| import { registerPlugins } from '@/plugins' | ||||
|  | ||||
| // Components | ||||
| import App from './App.vue' | ||||
| import ApiImplementation from './services/api-implementation'; | ||||
|  | ||||
| // Composables | ||||
| import { createApp } from 'vue' | ||||
|  | ||||
| process.env.API_IMPLEMENTATION = 'django'; | ||||
| let apiImplementation = new ApiImplementation(); | ||||
| const api = apiImplementation.getApi(); | ||||
|  | ||||
| const app = createApp(App); | ||||
| app.provide('api', api); | ||||
|  | ||||
| registerPlugins(app) | ||||
|  | ||||
| app.mount('#app') | ||||
							
								
								
									
										5
									
								
								src/pages/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/pages/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| # Pages | ||||
|  | ||||
| Vue components created in this folder will automatically be converted to navigatable routes. | ||||
|  | ||||
| Full documentation for this feature can be found in the Official [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) repository. | ||||
							
								
								
									
										7
									
								
								src/pages/comprar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/pages/comprar.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <template> | ||||
|   <Purchase /> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| // | ||||
| </script> | ||||
							
								
								
									
										20
									
								
								src/pages/cuadrar_tarro.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/pages/cuadrar_tarro.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <CodeDialog @code-verified="(verified) => showComponent = verified"/> | ||||
|   </div> | ||||
|   <ReconciliationJar v-if="showComponent" /> | ||||
| </template> | ||||
|  | ||||
| <script > | ||||
|   import CodeDialog from '../components/CodeDialog.vue' | ||||
|  | ||||
|   export default { | ||||
|     data() { | ||||
|       return { | ||||
|         showComponent: false, | ||||
|       } | ||||
|     }, | ||||
|     components: { CodeDialog }, | ||||
|     methods: {}, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										20
									
								
								src/pages/cuadres_de_tarro.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/pages/cuadres_de_tarro.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <CodeDialog @code-verified="(verified) => showComponent = verified" /> | ||||
|   </div> | ||||
|   <ReconciliationJarIndex v-if="showComponent" /> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
|   import CodeDialog from '../components/CodeDialog.vue' | ||||
|  | ||||
|   export default { | ||||
|     data() { | ||||
|       return { | ||||
|         showComponent: false, | ||||
|       } | ||||
|     }, | ||||
|     components: { CodeDialog }, | ||||
|     methods: {}, | ||||
|   } | ||||
| </script> | ||||
							
								
								
									
										6
									
								
								src/pages/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/pages/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <template> | ||||
|   <Wellcome /> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
| </script> | ||||
							
								
								
									
										7
									
								
								src/pages/summary_purchase.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/pages/summary_purchase.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <template> | ||||
|   <SummaryPurchase :id="$route.query.id"/> | ||||
| </template> | ||||
|  | ||||
| <script setup> | ||||
|   // | ||||
| </script> | ||||
							
								
								
									
										3
									
								
								src/plugins/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/plugins/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # Plugins | ||||
|  | ||||
| Plugins are a way to extend the functionality of your Vue application. Use this folder for registering plugins that you want to use globally. | ||||
							
								
								
									
										17
									
								
								src/plugins/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/plugins/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| /** | ||||
|  * plugins/index.js | ||||
|  * | ||||
|  * Automatically included in `./src/main.js` | ||||
|  */ | ||||
|  | ||||
| // Plugins | ||||
| import vuetify from './vuetify' | ||||
| import pinia from '@/stores' | ||||
| import router from '@/router' | ||||
|  | ||||
| export function registerPlugins (app) { | ||||
|   app | ||||
|     .use(vuetify) | ||||
|     .use(router) | ||||
|     .use(pinia) | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/plugins/vuetify.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/plugins/vuetify.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| /** | ||||
|  * plugins/vuetify.js | ||||
|  * | ||||
|  * Framework documentation: https://vuetifyjs.com` | ||||
|  */ | ||||
|  | ||||
| // Styles | ||||
| import '@mdi/font/css/materialdesignicons.css' | ||||
| import 'vuetify/styles' | ||||
|  | ||||
| // Composables | ||||
| import { createVuetify } from 'vuetify' | ||||
|  | ||||
| // https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides | ||||
| export default createVuetify({ | ||||
|   theme: { | ||||
|     defaultTheme: 'light', | ||||
|   }, | ||||
| }) | ||||
							
								
								
									
										37
									
								
								src/router/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/router/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
|  | ||||
| /** | ||||
|  * router/index.ts | ||||
|  * | ||||
|  * Automatic routes for `./src/pages/*.vue` | ||||
|  */ | ||||
|  | ||||
| // Composables | ||||
| import { createRouter, createWebHistory } from 'vue-router/auto' | ||||
| import { setupLayouts } from 'virtual:generated-layouts' | ||||
| import { routes } from 'vue-router/auto-routes' | ||||
|  | ||||
| const router = createRouter({ | ||||
|   history: createWebHistory(import.meta.env.BASE_URL), | ||||
|   routes: setupLayouts(routes), | ||||
| }) | ||||
|  | ||||
| // Workaround for https://github.com/vitejs/vite/issues/11804 | ||||
| router.onError((err, to) => { | ||||
|   if (err?.message?.includes?.('Failed to fetch dynamically imported module')) { | ||||
|     if (!localStorage.getItem('vuetify:dynamic-reload')) { | ||||
|       console.log('Reloading page to fix dynamic import error') | ||||
|       localStorage.setItem('vuetify:dynamic-reload', 'true') | ||||
|       location.assign(to.fullPath) | ||||
|     } else { | ||||
|       console.error('Dynamic import error, reloading page did not fix it', err) | ||||
|     } | ||||
|   } else { | ||||
|     console.error(err) | ||||
|   } | ||||
| }) | ||||
|  | ||||
| router.isReady().then(() => { | ||||
|   localStorage.removeItem('vuetify:dynamic-reload') | ||||
| }) | ||||
|  | ||||
| export default router | ||||
							
								
								
									
										21
									
								
								src/services/api-implementation.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/services/api-implementation.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| import DjangoApi from './django-api'; | ||||
| import Api from './api'; | ||||
|  | ||||
| class ApiImplementation { | ||||
|   constructor() { | ||||
|     const implementation = process.env.API_IMPLEMENTATION; | ||||
|     let apiImplementation; | ||||
|     if (implementation === 'django') { | ||||
|       apiImplementation = new DjangoApi(); | ||||
|     } else { | ||||
|       throw new Error("API implementation don't configured"); | ||||
|     } | ||||
|     this.api = new Api(apiImplementation); | ||||
|   } | ||||
|  | ||||
|   getApi() { | ||||
|     return this.api; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default ApiImplementation; | ||||
							
								
								
									
										51
									
								
								src/services/api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/services/api.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| class Api { | ||||
|   constructor (apiImplementation) { | ||||
|     this.apiImplementation = apiImplementation; | ||||
|   } | ||||
|  | ||||
|   getCustomers() { | ||||
|     return this.apiImplementation.getCustomers(); | ||||
|   } | ||||
|  | ||||
|   getProducts() { | ||||
|     return this.apiImplementation.getProducts(); | ||||
|   } | ||||
|  | ||||
|   getPaymentMethods() { | ||||
|     return this.apiImplementation.getPaymentMethods(); | ||||
|   } | ||||
|  | ||||
|   getSummaryPurchase(purchaseId) { | ||||
|     return this.apiImplementation.getSummaryPurchase(purchaseId); | ||||
|   } | ||||
|  | ||||
|   getPurchasesForReconciliation() { | ||||
|     return this.apiImplementation.getPurchasesForReconciliation(); | ||||
|   } | ||||
|  | ||||
|   getListReconcliations(page=1, itemsPerPage=10) { | ||||
|     return this.apiImplementation.getListReconcliations(page, itemsPerPage); | ||||
|   } | ||||
|  | ||||
|   getReconciliation(reconciliationId) { | ||||
|     return this.apiImplementation.getReconciliation(reconciliationId); | ||||
|   } | ||||
|  | ||||
|   isValidAdminCode(code) { | ||||
|     return this.apiImplementation.isValidAdminCode(code); | ||||
|   } | ||||
|  | ||||
|   createPurchase(purchase) { | ||||
|     return this.apiImplementation.createPurchase(purchase); | ||||
|   } | ||||
|  | ||||
|   createReconciliationJar(reconciliation) { | ||||
|     return this.apiImplementation.createReconciliationJar(reconciliation); | ||||
|   } | ||||
|  | ||||
|   createCustomer(customer) { | ||||
|     return this.apiImplementation.createCustomer(customer); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default Api; | ||||
							
								
								
									
										97
									
								
								src/services/django-api.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/services/django-api.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| class DjangoApi { | ||||
|   getCustomers() { | ||||
|     const url = '/don_confiao/api/customers/'; | ||||
|     return this.getRequest(url); | ||||
|   } | ||||
|  | ||||
|   getProducts() { | ||||
|     const url = '/don_confiao/api/products/'; | ||||
|     return this.getRequest(url); | ||||
|   } | ||||
|  | ||||
|   getPaymentMethods() { | ||||
|     const url = '/don_confiao/payment_methods/all/select_format'; | ||||
|     return this.getRequest(url); | ||||
|   } | ||||
|  | ||||
|   getSummaryPurchase(purchaseId) { | ||||
|     const url = `/don_confiao/resumen_compra_json/${purchaseId}`; | ||||
|     return this.getRequest(url); | ||||
|   } | ||||
|  | ||||
|   getPurchasesForReconciliation() { | ||||
|     const url = '/don_confiao/purchases/for_reconciliation'; | ||||
|     return this.getRequest(url); | ||||
|   } | ||||
|  | ||||
|   getListReconcliations(page, itemsPerPage) { | ||||
|     const url = `/don_confiao/api/reconciliate_jar/?page=${page}&page_size=${itemsPerPage}`; | ||||
|     return this.getRequest(url); | ||||
|   } | ||||
|  | ||||
|   getReconciliation(reconciliationId) { | ||||
|     const url = `/don_confiao/api/reconciliate_jar/${reconciliationId}/`; | ||||
|     return this.getRequest(url); | ||||
|   } | ||||
|  | ||||
|   isValidAdminCode(code) { | ||||
|     const url = `/don_confiao/api/admin_code/validate/${code}` | ||||
|     return this.getRequest(url) | ||||
|     } | ||||
|  | ||||
|   createPurchase(purchase) { | ||||
|     const url = '/don_confiao/api/sales/'; | ||||
|     return this.postRequest(url, purchase); | ||||
|   } | ||||
|  | ||||
|   createReconciliationJar(reconciliation) { | ||||
|     const url = '/don_confiao/reconciliate_jar'; | ||||
|     return this.postRequest(url, reconciliation); | ||||
|   } | ||||
|  | ||||
|   createCustomer(customer) { | ||||
|     const url = '/don_confiao/api/customers/'; | ||||
|     return this.postRequest(url, customer); | ||||
|   } | ||||
|  | ||||
|   getRequest(url) { | ||||
|     return new Promise ((resolve, reject) => { | ||||
|       fetch(url) | ||||
|         .then(response => response.json()) | ||||
|         .then(data => { | ||||
|           resolve(data); | ||||
|         }) | ||||
|         .catch(error => { | ||||
|           reject(error); | ||||
|         }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   postRequest(url, content) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|       fetch(url, { | ||||
|         method: 'POST', | ||||
|         headers: { | ||||
|           'Content-Type': 'application/json', | ||||
|         }, | ||||
|         body: JSON.stringify(content) | ||||
|       }) | ||||
|         .then(response => { | ||||
|           if (!response.ok) { | ||||
|             reject(new Error(`Error ${response.status}: ${response.statusText}`)); | ||||
|           } else { | ||||
|             response.json().then(data => { | ||||
|               if (!data) { | ||||
|                 reject(new Error('La respuesta no es un JSON válido')); | ||||
|               } else { | ||||
|                 resolve(data); | ||||
|               } | ||||
|             }); | ||||
|           } | ||||
|         }) | ||||
|         .catch(error => reject(error)); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   export default DjangoApi; | ||||
							
								
								
									
										5
									
								
								src/stores/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/stores/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| # Store | ||||
|  | ||||
| Pinia stores are used to store reactive state and expose actions to mutate it. | ||||
|  | ||||
| Full documentation for this feature can be found in the Official [Pinia](https://pinia.esm.dev/) repository. | ||||
							
								
								
									
										8
									
								
								src/stores/app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/stores/app.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| // Utilities | ||||
| import { defineStore } from 'pinia' | ||||
|  | ||||
| export const useAppStore = defineStore('app', { | ||||
|   state: () => ({ | ||||
|     // | ||||
|   }), | ||||
| }) | ||||
							
								
								
									
										4
									
								
								src/stores/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/stores/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| // Utilities | ||||
| import { createPinia } from 'pinia' | ||||
|  | ||||
| export default createPinia() | ||||
							
								
								
									
										3
									
								
								src/styles/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/styles/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # Styles | ||||
|  | ||||
| This directory is for configuring the styles of the application. | ||||
							
								
								
									
										10
									
								
								src/styles/settings.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/styles/settings.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| /** | ||||
|  * src/styles/settings.scss | ||||
|  * | ||||
|  * Configures SASS variables and Vuetify overwrites | ||||
|  */ | ||||
|  | ||||
| // https://vuetifyjs.com/features/sass-variables/` | ||||
| // @use 'vuetify/settings' with ( | ||||
| //   $color-pack: false | ||||
| // ); | ||||
		Reference in New Issue
	
	Block a user