Explorar el Código

[ADD] print engine support and some awesome features

robert hace 6 años
padre
commit
5dde9ad06b

+ 3 - 0
__openerp__.py

@@ -18,5 +18,8 @@
     'data': [
         'templates.xml',
         'security/eiru_pos_security.xml'
+    ],
+    'qweb': [
+        'static/report/ticket_template.xml'
     ]
 }

+ 3 - 2
controllers/account_journal.py

@@ -2,6 +2,7 @@
 from openerp.http import request
 from res_users import get_current_user
 from res_config import get_pos_config
+from res_bank_payment_type import get_bank_payment_types
 
 _MODEL = 'account.journal'
 
@@ -26,8 +27,8 @@ def get_journals():
             'name': journal.display_name,
             'code': journal.code,
             'type': journal.type,
-            'subtype': journal.subtype or None,
-            'currencyId': journal.currency.id or journal.company_id.currency_id.id
+            'currencyId': journal.currency.id or journal.company_id.currency_id.id,
+            'fieldsAllowed': get_bank_payment_types(journal.id)
         } for journal in request.env[_MODEL].search(domain, order='id')
     ]
 

+ 4 - 3
controllers/http_response.py

@@ -16,13 +16,13 @@ GZIP_COMPRESSION_LEVEL = 9
 '''
     Make JSON response
 '''
-def make_json_response(self, data=None, status=200):
+def make_json_response(data=None, status=200):
     return Response(json.dumps(data), status=status, content_type='application/json')
 
 '''
     Make GZIP to JSON response
 '''
-def make_gzip_response(self, data=None, status=200):
+def make_gzip_response(data=None, status=200):
     gzip_buffer = IO()
 
     with GzipFile(mode='wb', compresslevel=GZIP_COMPRESSION_LEVEL, fileobj=gzip_buffer) as gzip_file:
@@ -36,4 +36,5 @@ def make_gzip_response(self, data=None, status=200):
     headers.add('Vary', 'Accept-Encoding')
     headers.add('Content-Length', len(contents))
 
-    return Response(contents, status=status, headers=headers, content_type='application/json')
+    return Response(contents, status=status, headers=headers, content_type='application/json')
+    

+ 12 - 9
controllers/main.py

@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 from openerp import http
 from openerp.http import request
-from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
 
 from res_config import get_pos_config, save_pos_config
 from server_datetime import get_datetime, get_date
@@ -19,6 +18,7 @@ from account_move import create_account_move
 from account_voucher import create_account_voucher
 from account_bank_statement import create_bank_statement
 from res_bank_payment import create_bank_payment_statement
+from res_store import get_stores
 
 from http_response import make_gzip_response
 import logging
@@ -40,8 +40,8 @@ class PosSales(http.Controller):
         self.make_info_log('Sending JSON response')
         
         config = get_pos_config()
-
-        return make_gzip_response({
+        
+        data = {
             'settings': config,
             'date': get_datetime(),
             'user': get_current_user(),
@@ -52,8 +52,11 @@ class PosSales(http.Controller):
             'paymentTerms': get_payment_terms(),
             'banks': get_banks(),
             'bankPaymentTypes': get_bank_payment_types(),
-            'chequeTypes': get_cheque_types()
-        })
+            'chequeTypes': get_cheque_types(),
+            'stores': get_stores()
+        }
+    
+        return make_gzip_response(data)
     
     '''
         Get products data only
@@ -150,14 +153,14 @@ class PosSales(http.Controller):
         # Create bank statement
         create_bank_statement(account_voucher.id, date_now)
         self.make_info_log('[OK] Creating account bank statement')
-
+        
         # Create bank payment statement
-        if (kw.get('paymentMethod', False), ):
-            create_bank_payment_statement(bank_payment_data, currency_id, invoice.date_invoice, customer_id)
+        if kw.get('paymentMethod', 'Efectivo') == 'Banco':
+            create_bank_payment_statement(bank_payment_data, currency_id, invoice.date_invoice, journal_id, customer_id)
             self.make_info_log('[OK] Creating bank payment statement')
 
         return {
             'process': True,
             'name': sale_order.display_name,
-            'date': get_datetime
+            'date': get_datetime()
         }

+ 1 - 0
controllers/res_bank.py

@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 from openerp.http import request
+import simplejson as json
 
 _MODEL = 'res.bank'
 

+ 1 - 1
controllers/res_bank_cheque_type.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 from openerp.http import request
 
-_MODEL = 'res.bank.check.type'
+_MODEL = 'res.bank.cheque.type'
 
 '''
 '''

+ 14 - 10
controllers/res_bank_payment.py

@@ -1,22 +1,26 @@
 # -*- coding: utf-8 -*-
 from openerp.http import request
+from res_bank_payment_type import get_bank_payment_types
+from server_datetime import get_date
 
 _MODEL = 'res.bank.payments'
 
 '''
 '''
-def create_bank_payment_statement(data, currency_id, date, partner_id=None, supplier_id=None):
-    values = {    
-        'bank_id': data.get('bankId', None),
-        'bank_payments_type_id': data.get('bankPaymentTypeId', None),
+def create_bank_payment_statement(data, currency_id, date, journal_id=None, partner_id=None, supplier_id=None):
+    bank_payments_type_id = request.env['res.bank.payments.type'].get_bank_payments_type_id(journal_id)
+
+    values = {
+        'bank_payments_type_id': bank_payments_type_id,
+        'bank_id': data.get('bank_id', None),
         'currency_id': currency_id,
         'date': date,
-        'partner_id': partner_id,
-        'number_cta': data.get('accountNumber', None),
-        'number_cta_origin': data.get('originAccount', None),
-        'name_holder': data.get('accountHolder', None),
-        'number': data.get('voucherNumber'),
-        'date_maturity': str(data.get('dueDate', '')),
+        'customer_id': partner_id,
+        'number_cta': data.get('number_cta', None),
+        'number_cta_origin': data.get('number_cta_origin', None),
+        'name_holder': data.get('name_holder', None),
+        'number': data.get('number'),
+        'date_maturity': data.get('date_maturity', '') or get_date(),
         'supplier_id': supplier_id,
         'amount_total': data.get('amount', None)
     }

+ 2 - 0
controllers/res_bank_payment_line.py

@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+from openerp import models, fields, tools

+ 3 - 16
controllers/res_bank_payment_type.py

@@ -1,24 +1,11 @@
 # -*- coding: utf-8 -*-
 from openerp.http import request
+import simplejson as json
 
 _MODEL = 'res.bank.payments.type'
 
 '''
     Get bank payment types
 '''
-def get_bank_payment_types():
-    domain = [
-        ('is_receipt', '=', True)
-    ]
-
-    return [
-        {
-            'id': type.id,
-            'name': type.display_name,
-            'isReceipt': type.is_receipt,
-            'isPayment': type.is_payment,
-            'fieldsAllowed': map(lambda x: x.strip(), (type.fields_allowed or []).split(',')),
-            'subtype': type.subtype,
-            'defaultState': type.default_state
-        } for type in request.env[_MODEL].search(domain)
-    ]
+def get_bank_payment_types(journal_id=None):
+    return request.env[_MODEL].get_bank_payments_type(journal_id)

+ 3 - 2
controllers/res_config.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 from openerp.http import request
 
-_MODEL = 'base.config.settings'
+_MODEL = 'res.config.settings'
 
 '''
 '''
@@ -44,4 +44,5 @@ def save_pos_config(**kw):
     settings.write(values)
     settings.execute()
     
-    return get_pos_config()
+    return get_pos_config()
+    

+ 26 - 4
controllers/res_store.py

@@ -1,8 +1,30 @@
 # -*- coding: utf-8 -*-
+from openerp.http import request
+
+_MODEL = 'res.store'
 
 '''
 '''
-def get_res_store(self, **args):
-    return {
-        'stores': []
-    }
+def get_stores(company_id=None):
+    domain = []
+
+    if company_id:
+        domain.append(('company_id', '=', company_id))
+
+    store_obj = request.env[_MODEL]
+    store_ids = store_obj.search(domain)
+
+    return [{
+        'id': store.id,
+        'name': store.display_name,
+        'parent': {
+            'id': store.parent_id.id or None
+        },
+        'company': {
+            'id': store.company_id.id or None
+        },
+        'journals': [{
+            'id': journal.id,
+            'name': journal.display_name
+        } for journal in store.journal_ids]
+    } for store in store_ids]

+ 3 - 0
controllers/res_users.py

@@ -12,6 +12,9 @@ def get_current_user():
             'name': user.company_id.display_name,
             'phone': user.company_id.phone or None,
             'city': user.company_id.city or None,
+            'street': user.company_id.street or None,
+            'city': user.company_id.city or None,
+            'country': (user.company_id.country_id and user.company_id.country_id.name) or None, 
             'currencyId': user.company_id.currency_id.id or None
         },
         'isManager': 'Gerente' in user.groups_id.filtered(lambda g: g.category_id.name == 'Eiru POS').mapped(lambda g: g.name)

+ 2 - 1
controllers/sale_order.py

@@ -73,4 +73,5 @@ def confirm_sale_order(sale_order_id):
         'state': 'manual'
     })
 
-    return sale_order.action_button_confirm()
+    return sale_order.action_button_confirm()
+    

+ 11 - 3
controllers/utils.py

@@ -1,12 +1,20 @@
 # -*- coding: utf-8 -*-
 from openerp.http import request
 
-
 '''
     Check if module is installed
 '''
 def is_module_installed(module_name):
-    domain = [('name', '=', module_name), ('state', '=', 'installed')]
+    domain = [
+        ('name', '=', module_name),
+        ('state', '=', 'installed')
+    ]
+
     module = request.env['ir.module.module'].search(domain)
 
-    return len(module) != 0
+    return len(module) != 0
+
+'''
+'''
+def is_field_exists(model_name, field_name):
+    fields = request.env[model_name].fields_get([field_name])

+ 1 - 1
models/res_config.py

@@ -3,7 +3,7 @@ from openerp import api, models, fields
 from openerp.tools.safe_eval import safe_eval
 
 class ResConfig(models.TransientModel):
-    _inherit = 'base.config.settings'
+    _inherit = 'res.config.settings'
 
     image_type = fields.Selection(selection=[('small', 'Pequeña'), ('big', 'Grande')], default='small', string='Imagen', required=True)
     allow_currency_exchange = fields.Boolean(default=False, string='Permitir cambiar moneda')

+ 5 - 2
models/sale_order_line.py

@@ -1,7 +1,10 @@
-# -*- coding: utf-8 -*-
+#-*- coding: utf-8 -*-
+"""
+    SaleOrderLine Override
+"""
 from openerp import models, fields
 
 class SaleOrderLine(models.Model):
     _inherit = 'sale.order.line'
 
-    discount = fields.Float(digits=(12, 9))
+    discount = fields.Float(digits=(12, 9))

+ 30 - 9
src/App.vue

@@ -25,17 +25,22 @@
                 slot='footer'
                 slot-scope='props'
             )
-                div(
-                    class='wizard-footer-left'
-                )
+                .wizard-footer-left
                     wizard-button(
                         v-if='props.activeTabIndex > 0'
                         @click.native='goBack'
                         :style='props.fillButtonStyle'
                     ) Volver
-                div(
-                    class='wizard-footer-right'
-                )
+                .wizard-footer-center(v-show='showStoreSelector')
+                    .store-selector
+                        span
+                            strong Sucursal
+                        input-select(
+                            :options='stores.map(s => ({ id: s.id, name: s.name }))'
+                            :selected='selectedStore'
+                            @onSelect='selectStore'
+                        )
+                .wizard-footer-right
                     wizard-button(
                         v-if='!props.isLastStep'
                         class='wizard-footer-right'
@@ -71,7 +76,7 @@
     import CustomerStep from './components/steps/Customer'
     import PaymentStep from './components/steps/Payment'
     import SettingsModal from './components/modals/SettingsModal'
-    import { LoadingOverlay, SettingsButton } from './components/common'
+    import { LoadingOverlay, SettingsButton, InputSelect } from './components/common'
 
     export default {
         components: {
@@ -83,13 +88,17 @@
             PaymentStep,
             LoadingOverlay,
             SettingsButton,
-            SettingsModal
+            SettingsModal,
+            InputSelect
         },
         computed: mapGetters([
             'isSale',
             'footerButtonsVisibility',
             'settingsVisibility',
             'settings',
+            'stores',
+            'selectedStore',
+            'showStoreSelector',
             'isManager',
             'loading',
             'completed'
@@ -125,6 +134,7 @@
                 'toggleSettingsVisibility',
                 'changeSetting',
                 'changeInitialPayment',
+                'selectStore',
                 'createSale',
                 'resetSale'
             ])
@@ -159,7 +169,7 @@
         position: absolute
         .v--modal-overlay
             background: rgba(0, 0, 0, 0.7)
-            z-index: 9999
+            z-index: 1001
         .vue-form-wizard
             width: 100%
             height: 100%
@@ -192,6 +202,17 @@
                 height: 50px
                 position: absolute
                 bottom: 0
+                text-align: center
+                .wizard-footer-center
+                    width: 270px
+                    display: inline-block
+                    .store-selector
+                        span
+                            display: inline-block
+                        div
+                            width: 200px
+                            margin-left: 15px
+                            display: inline-block
                 .wizard-btn
                     width: 160px
                     height: 40px

+ 14 - 2
src/components/common/DropdownSearcher.vue

@@ -117,13 +117,25 @@
                 if (el !== target && !el.contains(target))  {
                     this.hideOptions()
                 }
-            }
+            },
+            onKeyUp(e) {
+                if (e.code === 'ArrowUp') {
+                    // TODO: move selection up
+                }
+
+                if (e.code === 'ArrowDown') {
+                    // TODO: move selection down
+                }
+            },
+
         },
         created() {
             document.addEventListener('click', this.onClickOutside)
+            document.addEventListener('keyup', this.onKeyUp)
         },
         destroyed() {
             document.removeEventListener('click', this.onClickOutside)
+            document.removeEventListener('keyup', this.onKeyUp)
         },
         data() {
             return {
@@ -194,5 +206,5 @@
                     h2
                         font-size: 10pt
                         font-weight: normal
-                        margin: 0
+                        margin: 0 5px
 </style>

+ 34 - 0
src/components/common/InputSelect.vue

@@ -0,0 +1,34 @@
+<template lang="pug">
+    div
+        select.input-select(@change='onSelect')
+            option(
+                v-for='o in options'
+                :value='o.id'
+            ) {{ o.name }}
+</template>
+
+<script>
+    export default {
+        props: {
+            selected: {
+                type: Object,
+                default: ''
+            },
+            options: {
+                type: Array,
+                default: []
+            }
+        },
+        methods: {
+            onSelect(e) {
+                this.$emit('onSelect', e.target.value)
+            }
+        }
+    }
+</script>
+
+<style lang="sass">
+    .input-select
+        width: 100%
+        height: 35px
+</style>

+ 1 - 1
src/components/common/LoadingOverlay.vue

@@ -2,7 +2,7 @@
     transition(name='fade')
         .loading-overlay(v-show='show')
             spinner(type='wave')
-            h2.loading-text Cargando, espere...
+            h2.loading-text Por favor, espere...
 </template>
 
 <script>

+ 3 - 1
src/components/common/index.js

@@ -8,6 +8,7 @@ import InputDropdown from './InputDropdown'
 import LoadingOverlay from './LoadingOverlay'
 import SettingsButton from './SettingsButton'
 import SwitchButtonInput from './SwitchButtonInput'
+import InputSelect from './InputSelect'
 
 export {
     CardGrid,
@@ -19,5 +20,6 @@ export {
     InputDropdown,
     LoadingOverlay,
     SettingsButton,
-    SwitchButtonInput
+    SwitchButtonInput,
+    InputSelect
 }

+ 82 - 146
src/components/modals/BankPaymentModal.vue

@@ -12,6 +12,7 @@
             title='Pago Bancario'
             subtitle=''
             color='#7c7bad'
+            ref='wizard'
         )
             tab-content(
                 title='Qué tipo de operación es?'
@@ -22,56 +23,42 @@
                     @onSelect='selectBankJournal'
                 )
             tab-content(title='Qué detalles necesita?')
-                form.payment-details-form
-                    .form-item
-                        label.form-label Tipo de Pago
-                        select.form-input(v-model='selectedBankPaymentTypeId')
-                            option(
-                                v-for='bankPaymentType in bankPaymentTypes'
-                                :value='bankPaymentType.id'
-                            ) {{ bankPaymentType.name }}
-                    .form-item(v-show="canBeShow('number_cta')")
-                        label.form-label Nº de Cuenta
-                        input.form-input(v-model='accountNumber')
-                    .form-item(v-show="canBeShow('date_maturity')")
-                        label.form-label Vencimiento
+                form.payment-details-form(v-if='selectedJournal')
+                    .form-item(v-for='(field, index) in selectedJournal.fieldsAllowed' :key='index')
+                        label.form-label {{ field.label }}
+                        input.form-input(
+                            v-if="field.string.typeField === 'char'"
+                            v-model='values[field.name]'
+                        )
                         date-picker.form-input(
-                            v-model='dueDate'
+                            v-else-if="field.string.typeField === 'date'"
+                            v-model='values[field.name]'
                             input-class='form-input'
                             lang='es'
+                            format='DD/MM/YYYY'
                             :editable='false'
                             :not-before="new Date()"
                         )
-                    .form-item(v-show="canBeShow('check_type_id')")
-                        label.form-label Tipo de Cheque
-                        select.form-input(v-model='selectedChequeTypeId')
-                            option(
-                                v-for='chequeType in chequeTypes'
-                                :value='chequeType.id'
-                            ) {{ chequeType.name }}
-                    .form-item(v-show="canBeShow('name_holder')")
-                        label.form-label Titular
-                        input.form-input(v-model='accountHolder')
-                    .form-item
-                        label.form-label Nº de Comprobante
-                        input.form-input(v-model='voucherNumber')
-                    .form-item(v-show="canBeShow('bank_id')")
-                        label.form-label Banco
                         dropdown-searcher.form-input(
+                            v-else-if="field.string.typeField === 'many2one' && field.name === 'bank_id'"
                             :items='banks'
-                            @onSelect='selectBank'
+                            @onSelect='selectedItem => onSelectInDropdown(field.name, selectedItem.id)'
+                        )
+                        dropdown-searcher.form-input(
+                            v-else-if="field.string.typeField === 'many2one' && field.name === 'cheque_type_id'"
+                            :items='chequeTypes'
+                            @onSelect='selectedItem => onSelectInDropdown(field.name, selectedItem.id)'
                         )
-                    .form-item(v-show="canBeShow('number')")
-                        label.form-label Nº de Vale
-                        input.form-input(v-model='voucherNumber')
-                    .form-item(v-show="canBeShow('number_cta_origin')")
-                        label.form-label Nº de Cuenta Origen
-                        input.form-input(v-model='originAccount')
             tab-content(title='De qué monto es la operación?')
                 form.payment-details-form
                     .form-item
                         label.form-label Monto del Pago
-                        input.form-input(v-model='amount')
+                        //- input.form-input(v-model='amount')
+                        input-dropdown.form-input(
+                            format='number'
+                            :value='values.amount'
+                            @onChangeValue='onChangeAmount'
+                        )
             template(
                 slot='footer'
                 slot-scope='props'
@@ -92,7 +79,7 @@
                         v-if='!props.isLastStep'
                         class='wizard-footer-right'
                         :style='props.fillButtonStyle'
-                        @click.native='props.nextTab()'
+                        @click.native='goNext'
                     ) Siguiente
                     wizard-button(
                         v-else
@@ -105,7 +92,7 @@
 <script>
     import { FormWizard, TabContent, WizardButton } from 'vue-form-wizard'
     import DatePicker from 'vue2-datepicker'
-    import { CardGrid, DropdownSearcher } from '../common'
+    import { CardGrid, DropdownSearcher, InputDropdown } from '../common'
 
     export default {
         props: {
@@ -121,74 +108,56 @@
                 type: Boolean,
                 required: true
             },
-            banks: {
-                type: Array,
+            selectedJournal: {
+                type: Boolean,
                 required: true
             },
-            bankPaymentTypes: {
+            banks: {
                 type: Array,
                 required: true
             },
-            selectedBankPaymentType: {
-                type: Object,
-                required: true
-            },
             chequeTypes: {
                 type: Array,
                 required: true
             },
-            selectedChequeType: {
-                type: Object,
-                required: true
-            },
             show: {
                 type: Boolean,
                 required: true
             }
         },
-        components: {
-            FormWizard,
-            TabContent,
-            WizardButton,
-            DropdownSearcher,
-            CardGrid,
-            DatePicker
-        },
         computed: {
-            selectedBankPaymentTypeId: {
-                get() {
-                    return this.selectedBankPaymentType && this.selectedBankPaymentType.id
-                },
-                set(value) {
-                    this.changeBankPaymentType(value)
-                }
-            },
-            selectedChequeTypeId: {
+            amount: {
                 get() {
-                    return this.selectedChequeType && this.selectedChequeType.id
+                    return this.values.amount
                 },
                 set(value) {
-                    this.changeChequeType(value)
+                    this.values.amount = value
                 }
             }
         },
+        components: {
+            FormWizard,
+            TabContent,
+            WizardButton,
+            DropdownSearcher,
+            CardGrid,
+            DatePicker,
+            InputDropdown
+        },
         watch: {
             show(value) {
+                this.values = {}
+                this.values.amount = this.initialAmount
+
                 if (!value) {
                     this.$modal.hide('payment-bank-modal')
                     return
                 }
 
-                if (this.journals.length === 0) {
-                    this.onCancel()
-                    this.notify('No hay diarios definidos para pagos bancarios')
-                    return
-                }
-
                 this.$modal.show('payment-bank-modal')
             },
             initialAmount(value) {
-                this.amount = value
+                this.values.amount = value
             }
         },
         methods: {
@@ -197,66 +166,60 @@
                     e.stop()
                 }
             },
-            checkJournalSelectionStep() {
-                if (!this.hasSelectedJournal) {
-                    this.notify('Debes seleccionar el tipo de operación')
-                    return false
+            goNext() {
+                if (this.journalStepIncomplete()) {
+                    this.$emit('onNotify', 'El tipo de operación seleccionado no está configurado')
+                    return
+                }
+
+                if (this.fieldsStepIncomplete()) {
+                    this.$emit('onNotify', 'Algunos campos requeridos están incompletos')
+                    return
                 }
 
-                if (this.bankPaymentTypes.length === 0) {
-                    this.notify('No hay tipos de pago definidos para éste tipo de operación')
-                    return false
+                if (this.amountStepIncomplete()) {
+                    this.$emit('onNotify', 'El monto no es válido')
+                    return
                 }
 
-                return true
+                this.$refs.wizard.nextTab()
             },
-            canBeShow(formItem) {
-                return this.selectedBankPaymentType && this.selectedBankPaymentType.fieldsAllowed.indexOf(formItem) !== -1
+            journalStepIncomplete() {
+                return this.$refs.wizard.activeTabIndex == 0 && !this.selectedJournal || !this.selectedJournal.fieldsAllowed.length
             },
-            selectBankJournal(journal) {
-                this.$emit('onChangeBankJournal', journal.id)
+            fieldsStepIncomplete() {
+                let completed = false;
+
+                for (let field of this.selectedJournal.fieldsAllowed) {
+                    if (field.string.required) {
+                        completed = !!this.values[field.name]
+                    }
+                }
+
+                return this.$refs.wizard.activeTabIndex == 1 && !completed
             },
-            changeBankPaymentType(id) {
-                this.$emit('onChangeBankPaymentType', id)
+            amountStepIncomplete() {
+                return false
             },
-            notify(message) {
-                this.$emit('onNotify', message)
+            selectBankJournal(journal) {
+                this.$emit('onSelectBankJounal', journal.id)
             },
-            changeChequeType(id) {
-                this.$emit('onChangeChequeType', id)
+            onSelectInDropdown(fieldName, selectedId) {
+                this.values[fieldName] = selectedId
             },
-            selectBank(bank) {
-                this.$emit('onSelectBank', bank.id)
+            onChangeAmount(value) {
+                this.values.amount = value
             },
             onDone() {
-                this.$emit('onDone', {
-                    voucherNumber: this.voucherNumber,
-                    dueDate: this.dueDate,
-                    accountHolder: this.accountHolder,
-                    originAccount: this.originAccount,
-                    amount: this.amount
-                })
+                this.$emit('onDone', this.values)
             },
             onCancel() {
                 this.$emit('onCancel')
             }
         },
-        mounted() {
-            this.accountNumber = ''
-            this.voucherNumber = ''
-            this.dueDate = ''
-            this.accountHolder = ''
-            this.originAccount =''
-            this.amount = 0
-        },
         data() {
             return {
-                accountNumber: '',
-                voucherNumber: '',
-                dueDate: '',
-                accountHolder: '',
-                originAccount: '',
-                amount: 0
+                values: {}
             }
         }
     }
@@ -279,33 +242,6 @@
                 align-items: center !important
                 .payment-details-form
                     width: 100%
-                    .form-item
-                        width: 100%
-                        height: 35px
-                        margin-bottom: 15px
-                        & > .form-label, > .form-input
-                            display: inline-block
-                            vertical-align: top
-                        .form-label
-                            width: 200px
-                            height: 35px
-                            font-size: 12pt
-                            line-height: 30px
-                        .form-input
-                            width: 400px
-                            height: 35px
-                            font-size: 12pt
-                            border-radius: 0
-                            color: #666
-                            &.input-only
-                                margin-left: 200px
-                                margin-bottom: 15px
-                        .form-item-option
-                            display: inline-block
-                            input
-                                width: 20px
-                                height: 20px
-                            label
-                                font-size: 12pt
-                                margin: 0 35px 15px 5px
+                    .form-input
+                        color: #4c4c4c
 </style>

+ 5 - 12
src/components/steps/Payment.vue

@@ -78,18 +78,12 @@
         bank-payment-modal(
             :initialAmount='amountToPay'
             :journals='bankJournals'
-            :hasSelectedJournal='hasBankJournalSelected'
+            :selectedJournal='selectedJournal'
             :banks='banks'
-            :bankPaymentTypes='bankPaymentTypes'
-            :selectedBankPaymentType='selectedBankPaymentType'
             :chequeTypes='chequeTypes'
-            :selectedChequeType='selectedChequeType'
             :show='showBankPayment'
+            @onSelectBankJounal='selectJournal'
             @onNotify='notify'
-            @onChangeBankJournal='changeBankJournal'
-            @onChangeBankPaymentType='changeBankPaymentType'
-            @onChangeChequeType='changeChequeType'
-            @onSelectBank='changeBank'
             @onDone='endBankPayment'
             @onCancel='cancelBankPayment'
         )
@@ -160,6 +154,9 @@
             },
             selectedCurrency() {
                 this.focusInitialPayment()
+            },
+            paymentMethod() {
+                this.focusInitialPayment()
             }
         },
         methods: {
@@ -170,10 +167,6 @@
                 this.changeBankPaymentData(data)
                 this.toggleBankPayment()
             },
-            changeBankJournal(journalId) {
-                this.selectJournal(journalId)
-                this.changeBankPaymentType()
-            },
             cancelBankPayment() {
                 this.toggleBankPayment()
                 this.changePaymentMethod('Efectivo')

+ 95 - 26
src/store/actions.js

@@ -104,7 +104,7 @@ const actions = {
     },
     updateImages({ commit, getters }, data) {
         const imageType = getters.settings.imageType ? 'small' : 'big'
-
+        
         if (imageType === data.imageType) {
             return
         }
@@ -127,40 +127,107 @@ const actions = {
 
         commit('setLoading', true)
 
-        return axios.post('/eiru_sales/process_sale', {
+        const data = {
+            mode: getters.mode,
+            items: getters.cartItems.map(item => {
+                return {
+                    id: item.id,
+                    quantity: item.quantity,
+                    price: item.price
+                }
+            }),
+            total: getters.cartTotal,
+            customerId: getters.selectedCustomer.id,
+            paymentTermId: getters.paymentTerm.id,
+            journalId: getters.selectedJournal.id,
+            payment: getters.initialPayment > getters.amountToPay ? getters.amountToPay : getters.initialPayment,
+            currencyId: getters.selectedCurrency.id,
+            paymentMethod: getters.paymentMethod,
+            bankPaymentData: {
+                ...getters.bankPaymentData
+            }
+        }
+
+        axios.post('/eiru_sales/process_sale', {
             jsonrpc: '2.0',
             method: 'call',
             params: {
-                mode: getters.mode,
-                items: getters.cartItems.map(item => {
-                    return {
-                        id: item.id,
-                        quantity: item.quantity,
-                        price: item.price
-                    }
-                }),
-                total: getters.cartTotal,
-                customerId: getters.selectedCustomer.id,
-                paymentTermId: getters.paymentTerm.id,
-                journalId: getters.selectedJournal.id,
-                payment: getters.initialPayment > getters.amountToPay ? getters.amountToPay : getters.initialPayment,
-                currencyId: getters.selectedCurrency.id,
-                paymentMethod: getters.paymentMethod,
-                bankPaymentData: {
-                    bankPaymentTypeId: (getters.selectedBankPaymentType && getters.selectedBankPaymentType.id) || null,
-                    chequeTypeId: (getters.selectedChequeType && getters.selectedChequeType.id) || null,
-                    bankId: (getters.selectedBank && getters.selectedBank.id) || null,
-                    ...getters.bankPaymentData
-                }
+                ...data
             }
-        }).then(() => {
-            commit('setLoading', false)
-            commit('setCompleted', true)
         }).catch(() => {
+            dispatch('notify', 'La transacción no terminó correctamente')
+        })
+
+        dispatch('printTicket', data).then(() => {
             commit('setLoading', false)
             commit('setCompleted', true)
         })
     },
+    printTicket({ getters }) {
+        const data = {
+            company: getters.companyName,
+            street: getters.user.company.street,
+            city: getters.user.company.city,
+            country: getters.user.company.country,
+            customer: getters.selectedCustomerName,
+            date: openerp.web.date_to_str(new Date()),
+            user: getters.user.name,
+            items: getters.cartItems.map(item => {
+                return {
+                    name: item.name.toUpperCase(),
+                    quantity: item.quantity,
+                    price: item.price,
+                    subtotal: item.quantity * item.price
+                }
+            }),
+            paymentMethod: getters.paymentMethod,
+            total: getters.amountToPay,
+            received: getters.amountResidual + getters.amountToPay,
+            residual: getters.amountResidual,
+            currencyPosition: getters.selectedCurrency.position,
+            currencyDecimalPlaces: getters.selectedCurrency.decimalPlaces,
+            currencyDecimalSeparator: getters.selectedCurrency.decimalSeparator,
+            currencySymbol: getters.currencySymbol
+        }
+
+        const wrapper = document.createElement('div')
+        wrapper.innerHTML = openerp.web.qweb.render('EiruPosTicket', {...data})
+        wrapper.setAttribute('id', 'ticket_wrapper')
+
+        var childElement = document.body.appendChild(wrapper)
+
+        const ticket_el = document.querySelector('.eiru_pos_ticket')
+        const ticket_width = 70
+        const measure_factor = 3.7793
+        const dpi_factor = 3.125
+
+        return new Promise(resolve => {
+            openerp.html2canvas(ticket_el, {
+                logging: false,
+                width: ticket_width * measure_factor,
+                scale: dpi_factor
+            }).then(function (canvas) {
+                childElement.remove()
+    
+                const dataURL = canvas.toDataURL('image/png')
+    
+                const width = (canvas.width / measure_factor) / dpi_factor
+                const height = (canvas.height / measure_factor) / dpi_factor
+    
+                const doc = new jsPDF({
+                    unit: 'mm',
+                    format: [height, ticket_width]
+                })
+    
+                doc.addImage(dataURL, 'PNG', 0, 0, width, height)
+                const data = doc.output('datauristring')
+    
+                openerp.printer_bridge.print(data)
+
+                resolve();
+            });
+        })
+    },
     resetSettings() {
         // Ignore this
     },
@@ -169,6 +236,8 @@ const actions = {
             if (!(rootState[key] instanceof Object)) {
                 continue
             }
+            
+            key = key.replace('Module', '')
 
             dispatch(`reset${key[0].toUpperCase()}${key.slice(1)}`)
         }

+ 24 - 22
src/store/index.js

@@ -6,17 +6,18 @@ import getters from '@/store/getters'
 import mutations from '@/store/mutations'
 import actions from '@/store/actions'
 
-import cart from '@/store/modules/cart'
-import currency from '@/store/modules/currency'
-import customer from '@/store/modules/customer'
-import date from '@/store/modules/date'
-import journal from '@/store/modules/journal'
-import payment from '@/store/modules/payment'
-import product from '@/store/modules/product'
-import user from '@/store/modules/user'
-import bank from '@/store/modules/bank'
-import bankPaymentType from '@/store/modules/bankPaymentType'
-import chequeType from './modules/chequeType'
+import cartModule from '@/store/modules/cart'
+import currencyModule from '@/store/modules/currency'
+import customerModule from '@/store/modules/customer'
+import dateModule from '@/store/modules/date'
+import journalModule from '@/store/modules/journal'
+import paymentModule from '@/store/modules/payment'
+import productModule from '@/store/modules/product'
+import userModule from '@/store/modules/user'
+import bankModule from '@/store/modules/bank'
+import bankPaymentTypeModule from '@/store/modules/bankPaymentType'
+import chequeTypeModule from './modules/chequeType'
+import storeModule from './modules/store'
 
 Vue.use(Vuex)
 
@@ -26,17 +27,18 @@ const store = new Vuex.Store({
     mutations,
     actions,
     modules: {
-        cart,
-        currency,
-        customer,
-        date,
-        journal,
-        payment,
-        product,
-        user,
-        bank,
-        bankPaymentType,
-        chequeType
+        cartModule,
+        currencyModule,
+        customerModule,
+        dateModule,
+        journalModule,
+        paymentModule,
+        productModule,
+        userModule,
+        bankModule,
+        bankPaymentTypeModule,
+        chequeTypeModule,
+        storeModule
     },
     strict: true
 })

+ 1 - 1
src/store/modules/bankPaymentType.js

@@ -29,7 +29,7 @@ const mutations = {
     },
     setSelectedBankPaymentType (state, id) {
         if (id) {
-            state.selectedBankPaymentType = this.getters.bankPaymentTypes.find(b => b.id === id)
+            state.selectedBankPaymentType = this.getters.bankPaymentTypes.find(b => b.journalIds.indexOf(id) != -1)
             return
         }
 

+ 0 - 87
src/store/modules/cart.js

@@ -5,43 +5,21 @@ const state = {
 }
 
 const getters = {
-    /**
-     * 
-     * @param {*} state 
-     */
     cartItems (state) {
         return state.cartItems
     },
-    /**
-     * 
-     * @param {*} state 
-     */
     cartTotal (state, getters) {
         return state.cartTotal
     },
-    /**
-     * 
-     * @param {*} state 
-     */
     itemToDiscount (state) {
         return state.itemToDiscount
     }
 }
 
 const mutations = {
-    /**
-     * 
-     * @param {*} state 
-     * @param {*} payload 
-     */
     setCart (state, payload) {
         state.cartItems = payload
     },
-    /**
-     * 
-     * @param {*} state 
-     * @param {*} payload 
-     */
     pushToCart (state, payload) {
         let productFound = state.cartItems.find(item => item.id === payload.id)
 
@@ -60,11 +38,6 @@ const mutations = {
 
         state.cartItems = [payload, ...state.cartItems]
     },
-    /**
-     * 
-     * @param {*} state 
-     * @param {*} payload 
-     */
     pullFromCart (state, payload) {
         let productFoundIndex = state.cartItems.findIndex(item => item.id === payload.item.id)
 
@@ -82,37 +55,17 @@ const mutations = {
             state.cartItems.splice(productFoundIndex, 1)
         }
     },
-    /**
-     * 
-     * @param {*} state 
-     * @param {*} payload 
-     */
     setCartTotal (state, payload) {
         state.cartTotal = payload
     },
-    /**
-     * 
-     * @param {*} state 
-     * @param {*} payload 
-     */
     setItemToDiscount (state, payload) {
         state.itemToDiscount = payload
     },
-    /**
-     * 
-     * @param {*} state 
-     * @param {*} payload 
-     */
     setItemPrice (state, payload) {
         let foundProduct = state.cartItems.find(item => item.id === state.itemToDiscount.id)
 
         foundProduct.price = payload
     },
-    /**
-     * 
-     * @param {*} state 
-     * @param {*} payload 
-     */
     resetPrice (state, payload) {
         let foundProduct = state.cartItems.find(item => item.id === payload.id)
         foundProduct.price = foundProduct.listPrice
@@ -120,46 +73,21 @@ const mutations = {
 }
 
 const actions = {
-    /**
-     * 
-     * @param {*} param0 
-     * @param {*} payload 
-     */
     addToCart ({ commit }, payload) {
         commit('pushToCart', payload)
     },
-    /**
-     * 
-     * @param {*} param0 
-     * @param {*} payload 
-     */
     decreaseFromCart ({ commit }, payload) {
         commit('pullFromCart', {
             item: payload,
             mode: 'partial'
         })
     },
-    /**
-     * 
-     * @param {*} param0 
-     * @param {*} payload 
-     */
     undoPrice ({ commit }, payload) {
         commit('resetPrice', payload)
     },
-    /**
-     * 
-     * @param {*} param0 
-     * @param {*} payload 
-     */
     changePrice ({ commit }, payload) {
         commit('setItemToDiscount', payload)
     },
-    /**
-     * 
-     * @param {*} param0 
-     * @param {*} payload 
-     */
     applyPrice ({ commit }, payload) {
         if (payload) {
             commit('setItemPrice', payload)
@@ -167,31 +95,16 @@ const actions = {
 
         commit('setItemToDiscount', null)
     },
-    /**
-     * 
-     * @param {*} param0 
-     * @param {*} payload 
-     */
     removeFromCart ({ commit }, payload) {
         commit('pullFromCart', {
             item: payload,
             mode: 'full'
         })
     },
-    /**
-     * 
-     * @param {*} param0 
-     * @param {*} payload 
-     */
     changeCartTotal ({ commit, dispatch }, payload) {
         commit('setCartTotal', payload)
         dispatch('changeAmountToPay', payload)
     },
-    /**
-     * 
-     * @param {*} param0 
-     * @param {*} payload 
-     */
     resetCart ({ commit }) {
         commit('setCart', [])
         commit('setCartTotal', 0)

+ 1 - 0
src/store/modules/payment.js

@@ -220,6 +220,7 @@ const actions = {
         }
 
         commit('setPaymentMethod', methodName)
+        dispatch('changeInitialPayment', 0)
     },
     changeAmountToPay({ commit }, amount) {
         commit('setAmountToPay', amount)

+ 60 - 0
src/store/modules/store.js

@@ -0,0 +1,60 @@
+const state = {
+    loadingStores: false,
+    stores: [],
+    selectedStore: null
+}
+
+const getters = {
+    loadingStores(state) {
+        return state.loadingStores
+    },
+    stores(state) {
+        return state.stores
+    },
+    selectedStore(state) {
+        return state.selectedStore || { name: null }
+    },
+    hasSelectedStore(state) {
+        return !!state.selectedStore
+    },
+    showStoreSelector(state) {
+        return state.stores.length > 1 
+    }
+}
+
+const mutations = {
+    setLoadingStores(state, loading) {
+        state.loadingStores = !!loading
+    },
+    setStores(state, stores) {
+        state.stores = stores
+    },
+    setSelectedStore(state, storeId) {
+        if (!storeId) {
+            state.selectedStore = null
+        }
+
+        state.selectedStore = state.stores.find(s => s.id == storeId)
+    }
+}
+
+const actions = {
+    initStores({ commit }, stores) {
+        commit('setStores', stores)
+        commit('setLoadingStores')
+    },
+    resetStore({ commit }) {
+        commit('setLoadingStores')
+        commit('setStores', [])
+    },
+    selectStore({ commit }, storeId) {
+        commit('setSelectedStore', storeId)
+    }
+}
+
+export default {
+    state,
+    getters,
+    actions,
+    mutations
+}

+ 0 - 3
src/store/mutations.js

@@ -22,9 +22,6 @@ const mutations = {
     },
     updateSettings(state, settings) {
         state.settings = settings
-    },
-    updateImages(state, values) {
-        console.log(values)
     }
 }
 

+ 79 - 0
static/report/ticket_style.css

@@ -0,0 +1,79 @@
+.eiru_pos_ticket {
+    width: 70mm;
+    background: #fff;
+    font-size: 12pt;
+    font-family: "Courier New", "Courier", "Lucida Sans Typewriter", "Lucida Typewriter", monospace;
+    overflow: hidden;
+    color: #000;
+}
+
+.eiru_pos_ticket .separator {
+    text-align: center;
+    font-size: 8pt;
+    margin-top: 18pt;
+    margin-bottom: 18pt;
+}
+
+.eiru_pos_ticket .company {
+    text-align: center;
+    font-size: 14pt;
+    margin-bottom: 0;
+}
+
+.eiru_pos_ticket .store {
+    text-align: center;
+    font-size: 10pt;
+    margin-top: 18pt;
+    margin-bottom: 0;
+}
+
+.eiru_pos_ticket .street {
+    text-align: center;
+    font-size: 8pt;
+    margin-top: 8pt;
+    margin-bottom: 0;
+}
+
+.eiru_pos_ticket .country {
+    text-align: center;
+    font-size: 8pt;
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+.eiru_pos_ticket .title {
+    text-align: center;
+    font-size: 8pt;
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+.eiru_pos_ticket .date {
+    font-size: 8pt;
+    margin-left: 12pt;
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+.eiru_pos_ticket .salesman {
+    font-size: 8pt;
+    margin-left: 12pt;
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+.eiru_pos_ticket .items_wrapper, .eiru_pos_ticket .details_wrapper {
+    width: 100%;
+    padding: 0 10pt;
+}
+
+.eiru_pos_ticket .items_wrapper .items, .eiru_pos_ticket .details_wrapper .details {
+    width: 100%;
+    font-size: 7.5pt;
+    margin: 0;
+    padding: 0;
+}
+
+.eiru_pos_ticket .items_wrapper .items tbody tr:nth-child(odd) td {
+    padding-top: 8pt;
+}

+ 64 - 0
static/report/ticket_template.xml

@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="pos" xml:space="preserve">
+    <t t-name="Separator">
+        <p class="separator">------------------------------------</p>
+    </t>
+    <t t-name="EiruPosTicket">
+        <div class="eiru_pos_ticket">
+            <p class="company"><t t-esc="company" /></p>
+            <p class="street"><t t-esc="street" /></p>
+            <p class="country"><t t-esc="city" /> - <t t-esc="country" /></p>
+            <t t-call="Separator" />
+            <div class="items_wrapper">
+                <table class="items">
+                    <thead>
+                        <tr>
+                            <th>DESCRIPCION</th>
+                            <th style="text-align: right;">PRECIO</th>
+                            <th style="text-align: right;">CANT.</th>
+                            <th style="text-align: right;">SUBTOTAL</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <t t-foreach="items" t-as="i">
+                            <tr>
+                                <td colspan="4"><t t-esc="i.name" /></td>
+                            </tr>
+                            <tr>
+                                <td></td>
+                                <td style="text-align: right;"><t t-esc="i.price" /></td>
+                                <td style="text-align: right;"><t t-esc="i.quantity" /></td>
+                                <td style="text-align: right;"><t t-esc="i.subtotal" /></td>
+                            </tr>
+                        </t>
+                    </tbody>
+                </table>
+            </div>
+            <t t-call="Separator" />
+            <div class="details_wrapper">
+                <table class="details">
+                    <tbody>
+                        <tr>
+                            <td>TOTAL</td>
+                            <td style="text-align: right;"><t t-esc="total" /></td>
+                        </tr>
+                        <tr>
+                            <td>ENTREGADO</td>
+                            <td style="text-align: right;"><t t-esc="received" /></td>
+                        </tr>
+                        <tr>
+                            <td>VUELTO</td>
+                            <td style="text-align: right;"><t t-esc="residual" /></td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+            <t t-call="Separator" />
+            <p class="date">Cliente: <t t-esc="customer" /></p>
+            <p class="date">Fecha: <t t-esc="date" /></p>
+            <p class="salesman">Vendedor: <t t-esc="user" /></p>
+            <t t-call="Separator" />
+            <p style="font-size: 8pt; text-align: center;">GRACIAS POR SU PREFERENCIA!!!</p>
+        </div>
+    </t>
+</templates>

+ 2 - 1
templates.xml

@@ -3,6 +3,7 @@
         <template id="eiru_pos.assets" name="Eiru Pos Assets" inherit_id="eiru_assets.assets">
             <xpath expr="." position="inside">
                 <link rel="stylesheet" href="/eiru_pos/static/src/main.css" />
+                <link rel="stylesheet" href="/eiru_pos/static/report/ticket_style.css" />
                 <script type="text/javascript" src="/eiru_pos/static/src/main.js" />
             </xpath>
         </template>
@@ -24,7 +25,7 @@
 
         <record id="eiru_pos.settings_form" model="ir.ui.view">
             <field name="name">Preferencias Eiru POS</field>
-            <field name="model">base.config.settings</field>
+            <field name="model">res.config.settings</field>
             <field name="arch" type="xml">
                 <form string="Eiru POS">
                     <header>