diff options
14 files changed, 160 insertions, 55 deletions
diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py index a05ab7bd36e..deac732fd9a 100644 --- a/addons/point_of_sale/__manifest__.py +++ b/addons/point_of_sale/__manifest__.py @@ -78,6 +78,7 @@ 'static/src/xml/Screens/ProductScreen/NumpadWidget.xml', 'static/src/xml/Screens/ProductScreen/ActionpadWidget.xml', 'static/src/xml/Screens/ProductScreen/CategoryBreadcrumb.xml', + 'static/src/xml/Screens/ProductScreen/CashBoxOpening.xml', 'static/src/xml/Screens/ProductScreen/CategoryButton.xml', 'static/src/xml/Screens/ProductScreen/CategorySimpleButton.xml', 'static/src/xml/Screens/ProductScreen/HomeCategoryBreadcrumb.xml', diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py index 94a1da906bc..c358f427496 100644 --- a/addons/point_of_sale/controllers/main.py +++ b/addons/point_of_sale/controllers/main.py @@ -26,7 +26,7 @@ class PosController(http.Controller): :returns: object -- The rendered pos session. """ domain = [ - ('state', '=', 'opened'), + ('state', 'in', ['opening_control', 'opened']), ('user_id', '=', request.session.uid), ('rescue', '=', False) ] @@ -39,7 +39,7 @@ class PosController(http.Controller): # session. if not pos_session and config_id: domain = [ - ('state', '=', 'opened'), + ('state', 'in', ['opening_control', 'opened']), ('rescue', '=', False), ('config_id', '=', int(config_id)), ] diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index 6759532f408..dce3b6445b4 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -143,7 +143,7 @@ class PosConfig(models.Model): help="The product categories will be displayed with pictures.") restrict_price_control = fields.Boolean(string='Restrict Price Modifications to Managers', help="Only users with Manager access rights for PoS app can modify the product prices on orders.") - cash_control = fields.Boolean(string='Cash Control', help="Check the amount of the cashbox at opening and closing.") + cash_control = fields.Boolean(string='Advanced Cash Control', help="Check the amount of the cashbox at opening and closing.") receipt_header = fields.Text(string='Receipt Header', help="A short text that will be inserted as a header in the printed receipt.") receipt_footer = fields.Text(string='Receipt Footer', help="A short text that will be inserted as a footer in the printed receipt.") proxy_ip = fields.Char(string='IP Address', size=45, @@ -296,7 +296,7 @@ class PosConfig(models.Model): @api.depends('session_ids') def _compute_current_session_user(self): for pos_config in self: - session = pos_config.session_ids.filtered(lambda s: s.state in ['new_session', 'opening_control', 'opened', 'closing_control'] and not s.rescue) + session = pos_config.session_ids.filtered(lambda s: s.state in ['opening_control', 'opened', 'closing_control'] and not s.rescue) if session: pos_config.pos_session_username = session[0].user_id.sudo().name pos_config.pos_session_state = session[0].state @@ -601,9 +601,7 @@ class PosConfig(models.Model): 'user_id': self.env.uid, 'config_id': self.id }) - if self.current_session_id.state == 'opened': - return self.open_ui() - return self._open_session(self.current_session_id.id) + return self.open_ui() def open_existing_session_cb(self): """ close session button diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py index 3e60d421c3f..68aaac0a6f2 100644 --- a/addons/point_of_sale/models/pos_session.py +++ b/addons/point_of_sale/models/pos_session.py @@ -16,7 +16,6 @@ class PosSession(models.Model): _inherit = ['mail.thread', 'mail.activity.mixin'] POS_SESSION_STATE = [ - ('new_session', 'New Session'), ('opening_control', 'Opening Control'), # method action_pos_session_open ('opened', 'In Progress'), # method action_pos_session_closing_control ('closing_control', 'Closing Control'), # method action_pos_session_close @@ -46,7 +45,7 @@ class PosSession(models.Model): state = fields.Selection( POS_SESSION_STATE, string='Status', required=True, readonly=True, - index=True, copy=False, default='new_session') + index=True, copy=False, default='opening_control') sequence_number = fields.Integer(string='Order Sequence Number', help='A sequence number that is incremented with each order', default=1) login_number = fields.Integer(string='Login Sequence Number', help='A sequence number that is incremented each time a user resumes the pos session', default=0) @@ -223,8 +222,7 @@ class PosSession(models.Model): res = super(PosSession, self.with_context(ctx).sudo()).create(values) else: res = super(PosSession, self.with_context(ctx)).create(values) - if not pos_config.cash_control: - res.action_pos_session_open() + res.action_pos_session_open() return res @@ -248,7 +246,14 @@ class PosSession(models.Model): values = {} if not session.start_at: values['start_at'] = fields.Datetime.now() - values['state'] = 'opened' + if session.config_id.cash_control: + last_sessions = self.env['pos.session'].search([('config_id', '=', self.config_id.id)]).ids + # last session includes the new one already. + if len(last_sessions) > 1: + self.cash_register_id.balance_start = self.env['pos.session'].browse(last_sessions[1]).cash_register_id.balance_end_real + values['state'] = 'opening_control' + else: + values['state'] = 'opened' session.write(values) return True @@ -1005,6 +1010,16 @@ class PosSession(models.Model): action['context']['default_pos_id'] = self.config_id.id return action + def set_cashbox_pos(self, cashbox_value, notes): + self.state = 'opened' + self.cash_register_id.balance_start = cashbox_value + if notes: + self.env['mail.message'].create({ + 'body': notes, + 'model': 'account.bank.statement', + 'res_id': self.cash_register_id.id, + }) + def action_view_order(self): return { 'name': _('Orders'), diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index b56ad3dad59..137465b54ad 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -3468,3 +3468,20 @@ td { color: #4f5b66; cursor: pointer; } + +.cashbox-input { + margin: auto; + line-height: 74px; + font-size: 16px; + border: solid 1px rgb(202, 202, 202); + border-top-width: 0px; + cursor: pointer; + padding-left: 30px; + padding-right: 30px; + flex-grow: 1; + flex-basis: 100%; +} + +.currencyCashBox { + font-size: 30px; +} diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/CashBoxOpening.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/CashBoxOpening.js new file mode 100644 index 00000000000..be42e45dabe --- /dev/null +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/CashBoxOpening.js @@ -0,0 +1,42 @@ +odoo.define('point_of_sale.CashBoxOpening', function(require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + const { Gui } = require('point_of_sale.Gui'); + + class CashBoxOpening extends PosComponent { + constructor() { + super(...arguments); + this.changes = {}; + this.defaultValue = this.env.pos.bank_statement.balance_start || 0; + this.symbol = this.env.pos.currency.symbol; + } + captureChange(event) { + this.changes[event.target.name] = event.target.value; + } + startSession() { + let cashOpening = this.changes.cashBoxValue? this.changes.cashBoxValue: this.defaultValue; + if(isNaN(cashOpening)) { + Gui.showPopup('ErrorPopup',{ + 'title': 'Wrong value', + 'body': 'Please insert a correct value.', + }); + return; + } + this.env.pos.bank_statement.balance_start = cashOpening; + this.env.pos.pos_session.state = 'opened'; + this.props.cashControl.cashControl = false; + this.rpc({ + model: 'pos.session', + method: 'set_cashbox_pos', + args: [this.env.pos.pos_session.id, cashOpening, this.changes.notes], + }); + } + } + CashBoxOpening.template = 'CashBoxOpening'; + + Registries.Component.add(CashBoxOpening); + + return CashBoxOpening; +}); diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js index 70c58bd5b86..8afe14ae673 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js @@ -7,6 +7,7 @@ odoo.define('point_of_sale.ProductScreen', function(require) { const { useListener } = require('web.custom_hooks'); const Registries = require('point_of_sale.Registries'); const { onChangeOrder, useBarcodeReader } = require('point_of_sale.custom_hooks'); + const { useState } = owl.hooks; class ProductScreen extends ControlButtonsMixin(PosComponent) { constructor() { @@ -31,6 +32,8 @@ odoo.define('point_of_sale.ProductScreen', function(require) { triggerAtInput: 'update-selected-orderline', useWithBarcode: true, }); + let status = this.showCashBoxOpening() + this.state = useState({ cashControl: status}); this.numpadMode = 'quantity'; this.mobile_pane = this.props.mobile_pane || 'right'; } @@ -54,6 +57,11 @@ odoo.define('point_of_sale.ProductScreen', function(require) { get currentOrder() { return this.env.pos.get_order(); } + showCashBoxOpening() { + if(this.env.pos.config.cash_control && this.env.pos.pos_session.state == 'opening_control') + return true; + return false; + } async _clickProduct(event) { if (!this.currentOrder) { this.env.pos.add_new_order(); diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 71d5453874d..8782c09457f 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -273,10 +273,10 @@ exports.PosModel = Backbone.Model.extend({ }, },{ model: 'pos.session', - fields: ['id', 'name', 'user_id', 'config_id', 'start_at', 'stop_at', 'sequence_number', 'payment_method_ids'], + fields: ['id', 'name', 'user_id', 'config_id', 'start_at', 'stop_at', 'sequence_number', 'payment_method_ids', 'cash_register_id', 'state'], domain: function(self){ var domain = [ - ['state','=','opened'], + ['state','in',['opening_control','opened']], ['rescue', '=', false], ]; if (self.config_id) domain.push(['config_id', '=', self.config_id]); @@ -350,6 +350,13 @@ exports.PosModel = Backbone.Model.extend({ self.pricelists = pricelists; }, },{ + model: 'account.bank.statement', + fields: ['id', 'balance_start'], + domain: function(self){ return [['id', '=', self.pos_session.cash_register_id[0]]]; }, + loaded: function(self, statement){ + self.bank_statement = statement[0]; + }, + },{ model: 'product.pricelist.item', domain: function(self) { return [['pricelist_id', 'in', _.pluck(self.pricelists, 'id')]]; }, loaded: function(self, pricelist_items){ @@ -603,7 +610,7 @@ exports.PosModel = Backbone.Model.extend({ self.barcode_reader.set_barcode_parser(barcode_parser); return barcode_parser.is_loaded(); }, - } + }, ], // loads all the needed data on the sever. returns a promise indicating when all the data has loaded. diff --git a/addons/point_of_sale/static/src/xml/Screens/ProductScreen/CashBoxOpening.xml b/addons/point_of_sale/static/src/xml/Screens/ProductScreen/CashBoxOpening.xml new file mode 100644 index 00000000000..27193bf93b8 --- /dev/null +++ b/addons/point_of_sale/static/src/xml/Screens/ProductScreen/CashBoxOpening.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<templates id="template" xml:space="preserve"> + + <t t-name="CashBoxOpening" owl="1"> + <div style="margin-top: -20px;"> + <br/> + <h1> + <span> + Pos closed. + </span><br/> + <span> + Set a cash opening + </span> + + </h1> + <h1>Opening amount:</h1> + <input name="cashBoxValue" class="cashbox-input" t-on-change="captureChange" t-att-value="defaultValue"/> + <span class="currencyCashBox" t-esc="symbol"/> + + <h1>Notes:</h1> + <textarea name="notes" style="width: 51%" t-on-change="captureChange"/><br/><br/> + + <span class="control-button" t-on-click="startSession()">Open</span> + + </div> + </t> + +</templates> diff --git a/addons/point_of_sale/static/src/xml/Screens/ProductScreen/ProductScreen.xml b/addons/point_of_sale/static/src/xml/Screens/ProductScreen/ProductScreen.xml index 619659ef11e..71f6dabadd3 100644 --- a/addons/point_of_sale/static/src/xml/Screens/ProductScreen/ProductScreen.xml +++ b/addons/point_of_sale/static/src/xml/Screens/ProductScreen/ProductScreen.xml @@ -5,24 +5,29 @@ <div class="product-screen screen" t-att-class="{ oe_hidden: !props.isShown }"> <div class="screen-full-width"> <div class="leftpane" t-if="!env.isMobile || mobile_pane === 'left'"> - <OrderWidget/> - <div class="pads"> - <div class="control-buttons"> - <t t-foreach="controlButtons" t-as="cb" t-key="cb.name"> - <t t-component="cb.component" t-key="cb.name" /> - </t> - </div> - <div class="subpads"> - <ActionpadWidget client="client"/> - <NumpadWidget /> + <t t-if="state.cashControl"> + <CashBoxOpening cashControl="state"/> + </t> + <t t-else=""> + <OrderWidget/> + <div class="pads"> + <div class="control-buttons"> + <t t-foreach="controlButtons" t-as="cb" t-key="cb.name"> + <t t-component="cb.component" t-key="cb.name" /> + </t> + </div> + <div class="subpads"> + <ActionpadWidget client="client"/> + <NumpadWidget /> + </div> </div> - </div> - <t t-if="env.isMobile"> - <MobileOrderWidget pane="mobile_pane" t-on-switchpane="switchPane"/> + <t t-if="env.isMobile"> + <MobileOrderWidget pane="mobile_pane" t-on-switchpane="switchPane"/> + </t> </t> </div> <div class="rightpane" t-if="!env.isMobile || mobile_pane === 'right'"> - <ProductsWidget/> + <ProductsWidget t-if="!state.cashControl"/> <t t-if="env.isMobile"> <MobileOrderWidget pane="mobile_pane" t-on-switchpane="switchPane"/> </t> diff --git a/addons/point_of_sale/views/point_of_sale_dashboard.xml b/addons/point_of_sale/views/point_of_sale_dashboard.xml index a7ae6586def..76609abe69c 100644 --- a/addons/point_of_sale/views/point_of_sale_dashboard.xml +++ b/addons/point_of_sale/views/point_of_sale_dashboard.xml @@ -68,9 +68,6 @@ <div class="o_primary"> <t t-esc="record.name.value"/> </div> - <t t-if="record.pos_session_state.raw_value == 'new_session'"> - <div class="badge badge-info o_kanban_inline_block">New session</div> - </t> <t t-if="!record.current_session_id.raw_value && record.pos_session_username.value"> <div class="badge badge-info o_kanban_inline_block">Opened by <t t-esc="record.pos_session_username.value"/></div> </t> diff --git a/addons/point_of_sale/views/pos_assets_common.xml b/addons/point_of_sale/views/pos_assets_common.xml index 38387b5dc51..7b9683c0505 100644 --- a/addons/point_of_sale/views/pos_assets_common.xml +++ b/addons/point_of_sale/views/pos_assets_common.xml @@ -58,6 +58,7 @@ <script type="text/javascript" src="/point_of_sale/static/src/js/Screens/ProductScreen/NumpadWidget.js"></script> <script type="text/javascript" src="/point_of_sale/static/src/js/Screens/ProductScreen/ActionpadWidget.js"></script> <script type="text/javascript" src="/point_of_sale/static/src/js/Screens/ProductScreen/CategoryBreadcrumb.js"></script> + <script type="text/javascript" src="/point_of_sale/static/src/js/Screens/ProductScreen/CashBoxOpening.js"></script> <script type="text/javascript" src="/point_of_sale/static/src/js/Screens/ProductScreen/CategoryButton.js"></script> <script type="text/javascript" src="/point_of_sale/static/src/js/Screens/ProductScreen/CategorySimpleButton.js"></script> <script type="text/javascript" src="/point_of_sale/static/src/js/Screens/ProductScreen/HomeCategoryBreadcrumb.js"></script> diff --git a/addons/point_of_sale/views/pos_config_view.xml b/addons/point_of_sale/views/pos_config_view.xml index df1a7ed9423..33c53bc0800 100644 --- a/addons/point_of_sale/views/pos_config_view.xml +++ b/addons/point_of_sale/views/pos_config_view.xml @@ -409,12 +409,6 @@ <div class="text-muted"> Control cash box at opening and closing </div> - <div class="content-group mt16" attrs="{'invisible': [('cash_control', '=', False)]}"> - <div class="row mt16"> - <label string="Default Opening" for="default_cashbox_id" class="col-lg-3 o_light_label" /> - <field name="default_cashbox_id" domain="[('is_a_template', '=', True)]" context="{'default_is_a_template': True, 'current_currency_id': currency_id}"/> - </div> - </div> <div attrs="{'invisible': [('cash_control', '=', False)]}"> <label for="amount_authorized_diff" string="Authorized Difference" class="font-weight-normal"/> <field name="amount_authorized_diff"/> diff --git a/addons/point_of_sale/views/pos_session_view.xml b/addons/point_of_sale/views/pos_session_view.xml index 9b11eb65a1b..f1187cc0006 100644 --- a/addons/point_of_sale/views/pos_session_view.xml +++ b/addons/point_of_sale/views/pos_session_view.xml @@ -9,9 +9,7 @@ <button name="open_cashbox_pos" type="object" string="Open Session" attrs="{'invisible' : ['|', ('cash_control', '=', False), ('state', '!=', 'new_session')]}" class="oe_highlight" context="{'balance': 'start'}"/> - <button name="action_pos_session_open" type="object" string="Start Session" - attrs="{'invisible' : ['|', ('cash_control', '=', False), ('state', '!=', 'opening_control')]}" class="oe_highlight"/> - <button name="open_frontend_cb" type="object" string="Continue Selling" states="opened"/> + <button name="open_frontend_cb" type="object" string="Continue Selling" states="opening_control,opened"/> <button id="end_session_opened" name="action_pos_session_closing_control" type="object" string="End of Session" attrs="{'invisible' : ['|', ('cash_control', '=', False),('state', '!=', 'opened')]}" class="oe_highlight"/> @@ -21,7 +19,7 @@ <button id="validate_closing_control" name="action_pos_session_validate" type="object" string="Close Session & Post Entries" states="closing_control" class="oe_highlight"/> - <field name="state" widget="statusbar" statusbar_visible="new_session,opening_control,opened,closing_control,closed" nolabel="1" /> + <field name="state" widget="statusbar" statusbar_visible="opened,closing_control,closed" nolabel="1" /> </header> <sheet> <field name="cash_register_id" invisible="1"/> @@ -73,35 +71,29 @@ <group class="col-md-12" style="margin-left: 140px;"> <group class="col-md-7"> <div style="margin:0;padding:0;"> - <group style="margin:0;padding:0;"> - <field style="text-align:right;margin:0;padding:0;" name="cash_register_balance_start" readonly="1" widget="monetary" options="{'currency_field': 'currency_id'}" string="Starting Cash"/> - </group> - <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', 'in', ['new_session', 'opening_control', 'closed'])]}"> + <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', 'in', ['opening_control', 'closed'])]}"> <field style="text-align:right;margin:0;padding:0;" name="cash_register_total_entry_encoding" widget="monetary" options="{'currency_field': 'currency_id'}" string="+ Transactions"/> </group> <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', '!=', 'closed')]}"> <field style="text-align:right;margin:0;padding:0;" name="cash_real_transaction" widget="monetary" options="{'currency_field': 'currency_id'}" string="+ Transactions"/> </group> - <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', 'in', ['new_session', 'opening_control', 'closed'])]}"> + <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', 'in', ['opening_control', 'closed'])]}"> <field style="text-align:right;margin:0;padding:0;" name="cash_register_balance_end" widget="monetary" options="{'currency_field': 'currency_id'}" string="= Expected in Cash"/> </group> <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', '!=', 'closed')]}"> <field style="text-align:right;margin:0;padding:0;" name="cash_real_expected" widget="monetary" options="{'currency_field': 'currency_id'}" string="= Expected"/> </group> - <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', 'in', ['new_session', 'opening_control'])]}"> + <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', 'in', ['opening_control'])]}"> <field style="text-align:right;margin:0;padding:0;" name="cash_register_balance_end_real" widget="monetary" options="{'currency_field': 'currency_id'}" string="Actual in Cash"/> </group> </div> <div style="margin:0;padding:0;"> - <group style="margin:0;padding:0;"> - <button class="oe_inline oe_link" name="open_cashbox_pos" type="object" context="{'balance': 'start'}" attrs="{'invisible':['|', ('cash_control', '=', False), ('state', '!=', 'opening_control')]}">Set Starting Cash</button> - </group> - <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', 'in', ['new_session', 'opening_control'])]}"> + <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', 'in', ['opening_control'])]}"> <button class="oe_inline oe_link" name="%(action_pos_box_out)d" type="action" attrs="{'invisible' : [('state', '=', 'closed')]}">Cash In/Out</button> </group> - <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', 'in', ['new_session', 'opening_control'])]}"> + <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', '=', 'opening_control')]}"> </group> - <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', 'in', ['new_session', 'opening_control'])]}"> + <group style="margin:0;padding:0;" attrs="{'invisible' : [('state', '=', 'opening_control')]}"> <button class="oe_inline oe_link" name="open_cashbox_pos" type="object" attrs="{'invisible': [('state', '!=', 'closing_control')]}" context="{'balance': 'close'}">Set Closing Cash</button> </group> </div> @@ -140,7 +132,7 @@ <field name="user_id" widget="many2one_avatar_user"/> <field name="start_at" /> <field name="stop_at" /> - <field name="state" widget="badge" decoration-info="state in ('new_session', 'opening_control')" decoration-success="state in ('opened', 'closed')" decoration-warning="state == 'closing_control'" /> + <field name="state" widget="badge" decoration-info="state in ('opening_control')" decoration-success="state in ('opened', 'closed')" decoration-warning="state == 'closing_control'" /> </tree> </field> </record> |
