Develop Point of Sale App

Qua các phần trước, chúng ta đã khám phá hai cơ sở code khác nhau. Đầu tiên là code phía phần backend, nó dùng để tạo ra các views, actions, menus,... Thứ hai là code phía phần frontend, nó dùng để tạo ra các web page, controller, snippets,... Ở phần này, chúng ta sẽ khám phá cơ sở code thứ ba, được sử dụng cho ứng dụng Điểm bán hàng. Bạn có thể thắc mắc tại sao ứng dụng Điểm bán hàng lại cần một cơ sở code khác. Điều này là do nó sử dụng một kiến trúc khác, để hoạt động ngoại tuyến. Đồng thời, ta sẽ xem cách sửa đổi ứng dụng Điểm bán hàng.

Các mục chính trong bài viết này:

Ghi chú

Ứng dụng Điểm bán hàng chủ yếu được viết bằng JavaScript. Bài viết này hướng dẫn viết với giả định rằng bạn đã có kiến thức cơ bản về JavaScript. Bên cạnh đó cũng sử dụng OWL framework, vì vậy nếu bạn không biết về các thuật ngữ JavaScript này, xem The Odoo Web Library (OWL).

Trong suốt phần này, chúng ta sẽ sử dụng một Module bổ trợ có tên là viin_pos. Module viin_pos sẽ phụ thuộc vào point_of_sale vì chúng ta sẽ đi tùy chỉnh ứng dụng Điểm bán hàng.

Thêm tệp JavaScript/SCSS tùy chỉnh

Ứng dụng Điểm bán hàng sử dụng các gói Assets khác nhau để quản lý các tệp JavaScript và style sheet. Trong công thức này, chúng ta sẽ tìm hiểu cách thêm các tệp SCSS và JavaScript vào Asset Bundles của Điểm bán hàng.

Sẵn sàng

Trong phần này, chúng ta sẽ tải SCSS style sheet và tệp JavaScript vào Ứng dụng Điểm bán hàng

Các bước thực hiện

Để tải nội dung trong ứng dụng Điểm bán hàng, hãy làm theo các bước sau:

  1. Thêm tệp SCSS mới tại /viin_pos/static/src/scss/viin_pos.scss và chèn mã sau:

.pos .pos-content {
     .price-tag {
     background-color: black;
     }
}
  1. Thêm tệp JavaScript mới tại /viin_pos/static/src/js/viin_pos.js và chèn mã sau:

console.log('Viindoo Pos');
  1. Đăng ký các tệp JavaScript và SCSS này vào point_of_sale assets:

<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="assets" inherit_id="point_of_sale.assets">
        <xpath expr="." position="inside">
            <script type="text/javascript" src="/viin_pos/static/src/js/viin_pos.js"></script>
            <link rel="stylesheet" href="/viin_pos/static/src/scss/viin_pos.scss"/>
        </xpath>
    </template>
</odoo>

Cài đặt Module viin_pos. Để xem các thay đổi của bạn trong hành động, hãy bắt đầu phiên mới từ Menu Điểm bán hàng | Bảng điều khiển.

Cơ chế hoạt động

Trong công thức này, chúng ta đã tải một tệp JavaScript và một tệp SCSS vào Ứng dụng Điểm bán hàng. Trong bước 1, chúng ta đã thay đổi màu nền của nhãn giá của thẻ sản phẩm. Sau khi cài đặt Module viin_pos, bạn sẽ có thể để xem các thay đổi đối với nhãn giá:

Point Of Sale Image

Hình 22.1 - Nhãn giá được cập nhật

Trong bước 2, chúng ta đã thêm tệp JavaScript. Trong đó, chúng ta đã thêm log vào bảng điều khiển. Để mà xem thông báo, bạn sẽ cần mở các công cụ dành cho nhà phát triển của trình duyệt. Trong tab Bảng điều khiển, hiển thị ra log sau Viindoo Pos. Điều này cho thấy rằng tệp JavaScript của bạn đã được tải thành công. Hiện tại, chúng ta mới chỉ thêm đoạn log vào tệp JavaScript, nhưng trong các phần sắp tới, chúng ta sẽ thêm nhiều hơn vào nó:

Point Of Sale Image

Hình 22.2 - JavaScript được tải

Trong bước 3, chúng ta thêm tệp JavaScript và tệp SCSS vào nội dung Điểm bán hàng. Các External ID của assets Điểm bán hàng là point_of_sale.assets. Ở đây, chỉ có External ID là khác nhau; mọi thứ khác hoạt động như các assets thông thường. Nếu bạn không biết assets hoạt động thế nào trong Odoo, hãy tham khảo phần quản lý Static-assets trong CMS Phát triển trang web.

Thêm nút tác vụ trên bàn phím

Như chúng ta đã thảo luận trong phần trước, ứng dụng Điểm bán hàng được thiết kế theo cách cách hoạt động ngoại tuyến. Nhờ đó, cấu trúc mã của ứng dụng Điểm bán hàng khác với các ứng dụng Odoo còn lại. Cơ sở mã của ứng dụng Điểm bán hàng phần lớn được viết bằng JavaScript và cung cấp các tiện ích khác nhau để tùy chỉnh. Trong phần này, chúng ta sẽ sử dụng một tiện ích như vậy và tạo một nút tác vụ ở đầu bàn phím bảng điều khiển.

Sẵn sàng

Trong phần này, chúng ta sẽ sử dụng Module viin_pos được tạo trong phần Thêm tệp JavaScript/SCSS tùy chỉnh. Chúng ta sẽ thêm một nút ở đầu bảng điều khiển bàn phím. Điều này nút sẽ là một phím tắt để áp dụng chiết khấu cho các dòng đơn hàng.

Các bước thực hiện

Làm theo các bước sau để thêm nút hành động 10% Discount vào bảng điều khiển bàn phím cho ứng dụng điểm bán hàng:

  1. Thêm tệp đoạn code sau vào tệp /static/src/js/viin_pos.js, nó sẽ định nghĩa ra nút hành động:

odoo.define('viin_pos.discountbutton', function(require) {"use strict";

const PosComponent = require('point_of_sale.PosComponent');
const ProductScreen = require('point_of_sale.ProductScreen');
const Registries = require('point_of_sale.Registries');
class ViinPosDiscountButton extends PosComponent {
    async _setDIscount() {
        const order = this.env.pos.get_order();
        order.selected_orderline.set_discount(10);
    }
}
ViinPosDiscountButton.template = 'ViinPosDiscountButton';
ProductScreen.addControlButton({
    component: ViinPosDiscountButton,
    condition: function() {
        return true;
    },
});
Registries.Component.add(ViinPosDiscountButton);
return ViinPosDiscountButton;
});
  1. Thêm mẫu QWeb cho nút trong /static/src/xml/viin_pos.xml:

<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
    <t t-name="ViinPosDiscountButton" owl="1">
        <span class="control-button" t-on-click="_setDIscount">
            <span>10% Discount</span>
        </span>
    </t>
</templates>
  1. Đăng ký mẫu QWeb trong tệp manifest như sau:

'qweb': [
    'static/src/xml/viin_pos.xml'
]
  1. Cập nhật module viin_pos để áp dụng các thay đổi. Sau đó, bạn sẽ có thể xem nút 10% Discount phía trên bàn phím:

Point Of Sale Image

Hình 22.3 - Nút giảm giá

Sau khi nhấp vào đây, chiết khấu sẽ được áp dụng cho dòng đặt hàng đã chọn.

Cơ chế hoạt động

Ở Odoo v14, code của ứng dụng điểm bán hàng được viết lại hoàn toàn bằng cách sử dụng khung OWL. Bạn có thể tìm hiểu thêm về khung OWL trong Thư viện Web Odoo (OWL).

Để tạo nút hành động trong ứng dụng Điểm bán hàng, bạn sẽ cần mở rộng PosComponent. Bây giờ, PosComponent được định nghĩa trong point_of_sale. Không gian tên PosComponent, Để sử dụng nó trong code của bạn, bạn sẽ cần thêm nó. Trong bước 1, chúng ta đã nhập vào màn hình với require('point_of_sale.PosComponent'). Sau đó, chúng ta tạo PosDiscountButton bằng cách mở rộng PosComponent. Nếu bạn muốn tìm hiểu cơ chế require hoạt động trong Odoo JavaScript, tham khảo Thêm CSS và JavaScript cho một website trong Phát triển Trang web CMS. Trong bước 1, chúng ta cũng đã nhập point_of_sale.ProductScreenpoint_of_sale.Registries. Bây giờ, point_of_sale.ProductScreen được sử dụng để thêm một nút vào màn hình Điểm bán hàng thông qua phương thức addControlButton. Cuối cùng, chúng ta đã thêm một nút được đăng ký tới point_of_sale.Registries, là đăng ký toàn cục chứa tất cả các thành phần OWL.

PosComponent có một số tiện ích tích hợp cho phép truy cập thông tin hữu ích như chi tiết đơn đặt hàng, cấu hình Điểm bán hàng và hơn thế nữa. Bạn có thể truy cập nó qua biến this.env. Trong ví dụ của chúng ta, chúng ta đã truy cập thông tin đặt hàng hiện tại thông qua cái này. phương thức env.pos.get_order(). Sau đó, chúng ta sử dụng phương thức set_discount() để thiết lập chiết khấu 10%.

Trong bước 2 và bước 3, chúng ta đã thêm mẫu OWL, mẫu này sẽ được hiển thị trên bàn phím ứng dụng Điểm Bán. Nếu bạn muốn tìm hiểu thêm về điều này, vui lòng tham khảo The Odoo Thư viện Web (OWL).

Mở rộng

Phương thức addControlButton() hỗ trợ một đối số nữa, đó là condition. Đối số này được sử dụng để ẩn/hiện nút dựa trên một số điều kiện. Giá trị của đối số này là một hàm trả về Boolean. Dựa trên giá trị trả lại, hệ thống Điểm bán hàng sẽ ẩn hoặc hiện nút. Hãy xem ví dụ sau đây:

ProductScreen.addControlButton({
    component: ViinPosDiscountButton,
    condition: function() {
        return this.env.pos.config.module_pos_discount;
    },
});

Nút giảm giá theo điều kiện chỉ được hiển thị nếu chiết khấu được cho phép từ Cấu hình điểm bán hàng.

Thực hiện gọi RPC

Mặc dù ứng dụng Điểm bán hàng hoạt động ngoại tuyến, vẫn có thể thực hiện các cuộc gọi RPC đến máy chủ. Cuộc gọi RPC có thể được sử dụng cho bất kỳ hoạt động nào; bạn có thể sử dụng nó cho CRUD hoặc để thực hiện một hành động trên máy chủ. Trong phần này, chúng ta sẽ thực hiện một cuộc gọi RPC để lấy thông tin về hai đơn hóa đơn gần đây nhất của khách hàng.

Sẵn sàng

Trong phần này, chúng ta sẽ sử dụng module viin_pos được tạo trong phần Thêm nút tác vụ trên bàn phím. chúng ta sẽ xác định nút hành động. Khi người dùng nhấp vào nút hành động, chúng ta sẽ thực hiện một cuộc gọi RPC để tìm nạp thông tin đơn hóa đơn và hiển thị nó trên cửa sổ bật lên.

Các bước thực hiện

Thực hiện theo các bước sau để hiển thị hai hóa đơn cuối cùng của khách hàng:

  1. Thêm đoạn code sau vào tệp /static/src/js/viin_pos.js; điều này sẽ thêm một nút hành động mới để tìm nạp và hiển thị thông tin về hai hóa đơn khi người dùng nhấp vào nút:

class PostLastInvoice extends PosComponent {
// Đặt bước 2 ở đây
}
PostLastInvoice.template = 'PostLastInvoice';
ProductScreen.addControlButton({
    component: PostLastInvoice,
    condition: function () {
        return true;
    },
});
Registries.Component.add(PostLastInvoice);
  1. Thêm hàm _showLastInvoice vào nút thành phần PostLastInvoice để quản lý nút nhấp chuột:

_showLastInvoice() {
    var self = this;
    const order = this.env.pos.get_order();
    if (order.attributes.client) {
        var domain = [
            ['partner_id', '=', order.attributes.client.id]
        ];
        this.rpc({
            model: 'account.move',
            method: 'search_read',
            args: [domain, ['name', 'amount_total']],
            kwargs: {
                limit: 2
            },
        }).then(function (moves) {
            if (moves.length > 0) {
                var order_list = _.map(moves, function (o) {
                    return {
                        'label': _.str.sprintf("%s -AMOUNT: %s", o.name, o.amount_total)
                    };
                });
                self.showPopup('SelectionPopup', {
                    title: 'Last 2 invoices',
                    list: order_list
                });
            } else {
                self.showPopup('ErrorPopup', {
                    body: 'No invoices found'
                });
            }
        });
    } else {
        self.showPopup('ErrorPopup', {
            body: 'You must select the customer'
        });
    }
}
  1. Thêm mẫu QWeb cho nút vào tệp /static/src/xml/viin_pos.xml:

<t t-name="PostLastInvoice" owl="1">
    <span class="control-button" t-on-click="_showLastInvoice">
        <span>Last Invoices</span>
    </span>
</t>
  1. Cập nhật module viin_pos để áp dụng các thay đổi. Sau đó, bạn sẽ có thể xem nút Last Invoices phía trên bảng điều khiển bàn phím. Khi nút này được nhấp, cửa sổ bật lên sẽ được hiển thị với thông tin hóa đơn:

Point Of Sale Image

Hình 22.3 - Nút giảm giá

Nếu không tìm thấy hóa đơn nào trước đó, cảnh báo sẽ được hiển thị.

Cơ chế hoạt động

Ở bước 1, chúng ta đã tạo và đăng ký nút hành động. Nếu bạn muốn tìm hiểu thêm về nút hành động, hãy tham khảo phần Thêm nút tác vụ trên bàn phím của phần này. Trước khi đi vào chi tiết kỹ thuật, hãy hiểu những gì chúng ta muốn đạt được với nút hành động này. Sau khi nhấp vào, chúng ta muốn hiển thị thông tin cho hai hóa đơn cho khách hàng đã chọn. Sẽ có một vài trường hợp khách hàng không chọn, hoặc khách hàng không có hóa đơn nào trước đó. Trong những trường hợp như vậy, ta sẽ hiển thị một cửa sổ bật lên với một thông điệp thích hợp.

Tiện ích RPC có sẵn với thuộc tính this.rpc của component. Trong bước 2, chúng ta đã thêm chức năng xử lý nhấp chuột. Khi nhấp vào nút hành động, trình xử lý nhấp chuột hàm sẽ được gọi. Hàm này sẽ làm cho RPC gọi vào máy chủ để tìm nạp đơn thông tin đặt hàng. Chúng ta đã sử dụng phương thức rpc() để thực hiện các cuộc gọi RPC. Sau đây là danh sách các các đối số bạn có thể truyền trong phương thức rpc():

  • model: Tên của model mà bạn muốn thực hiện thao tác trên đó.

  • method: Tên của phương thức bạn muốn gọi.

  • args: Danh sách các đối số bắt buộc được phương thức chấp nhận.

  • kwargs: Một dictionary của các đối số tùy chọn được phương thức chấp nhận.

Trong phần này, chúng ta sử dụng phương thức search_read để tìm nạp dữ liệu thông qua RPC. Chúng ta dùng domain khách hàng để lọc các hóa đơn. Chúng ta cũng dùng đối số limit để lấy xuống chỉ hai hóa đơn. rpc.query() là một phương thức không đồng bộ và trả về đối tượng Promise, để xử lý kết quả, bạn sẽ cần sử dụng phương thức then(), hoặc bạn có thể sử dụng từ khóa await.

Ghi chú

Gọi RPC không hoạt động ở chế độ ngoại tuyến. Nếu mạng internet của bạn kết nối tốt và bạn không sử dụng chế độ ngoại tuyến thường xuyên, bạn có thể sử dụng RPC. Mặc dù ứng dụng Điểm bán hàng hoạt động ngoại tuyến, nhưng một vài thao tác, chẳng hạn như tạo hoặc cập nhật khách hàng yêu cầu phải kết nối internet thì cần sử dụng RPC để gọi nội bộ.

Chúng ta đã hiển thị thông tin đặt hàng trước đó trong cửa sổ bật lên. Chúng ta đã dùng SelectionPopup, nó được sử dụng để hiển thị danh sách có thể chọn; chúng ta sử dụng nó để hiển thị hai hóa đơn cuối cùng. Chúng ta cũng đã dùng ErrorPopup để hiển thị thông báo cảnh báo khi khách hàng không được chọn hoặc không có hóa đơn trước đó được tìm thấy.

Trong bước 3, chúng ta đã thêm mẫu QWeb cho nút hành động. Ứng dụng Điểm bán hàng sẽ kết xuất mẫu này để hiển thị nút hành động.

Mở rộng

Có rất nhiều tiện ích Popup khác. Ví dụ: NumberPopup được sử dụng để lấy nhập số từ người dùng. Tham khảo các tệp trong thư mục addons/point_of_sale/static/src/xml/Popups để xem tất cả các tiện ích này.

Sửa đổi UI màn hình Điểm bán hàng

Giao diện người dùng của ứng dụng Điểm bán hàng được viết bằng mẫu OWL QWeb. Trong phần này, chúng ta sẽ tìm hiểu cách bạn có thể sửa đổi các phần tử UI trong ứng dụng Điểm bán hàng.

Sẵn sàng

Trong phần này, chúng ta sẽ sử dụng module viin_pos được tạo trong Thực hiện gọi RPC. Chúng ta sẽ sửa đổi UI, hiển thị thêm mã sản phẩm.

Các bước thực hiện

Làm theo các bước sau để hiển thị thêm mã sản phẩm trên thẻ sản phẩm:

  1. Thêm đoạn code sau vào tệp /static/src/js/viin_pos.js để tìm nạp trường bổ sung trường mã sản phẩm của sản phẩm:

const pos_model = require('point_of_sale.models');
pos_model.load_fields("product.product", ["default_code"]);
  1. Thêm đoạn code sau vào /static/src/xml/viin_pos.xml để hiển thị mã sản phẩm:

<t t-name="ProductItem" t-inherit="point_of_sale.ProductItem" t-inherit-mode="extension" owl="1">
    <xpath expr="//div[hasclass('product-name')]" position="after">
        <span t-if="props.product.default_code" class="default_code_product">
            <t t-esc="props.product.default_code"/>
        </span>
    </xpath>
</t>
  1. Thêm style sheet sau để tạo kiểu cho mã sản phẩ:

.default_code_product {
    top: 2px;
    line-height: 15px;
    left: 2px;
    background: pink;
    position: absolute;
}

Cập nhật module viin_pos để áp dụng các thay đổi:

Point Of Sale Image

Hình 22.3 - Mã sản phẩm

Sửa đổi logic nghiệp vụ

Trong phần này, chúng ta sẽ tìm hiểu làm thế nào để mở rộng hoặc thay đổi logic nghiệp vụ hiện tại.

Sẵn sàng

Chúng ta sẽ hiển thị cảnh báo cho người dùng nếu họ bán số lượng sản phẩm nhiều hơn 5

Các bước thực hiện

Chúng ta sẽ thêm code JavaScript vào /static/src/js/viin_pos.js để hiển thị cảnh báo cho người dùng nếu bán nhiều hơn số lượng 5

const UpdatedProductScreen = ProductScreen =>
    class extends ProductScreen {
        _setValue(val) {
            super._setValue(val);
            const orderline = this.env.pos.get_order().selected_orderline;
            if (orderline && orderline.quantity >= 5) {
                this.showPopup('ErrorPopup', {
                    title:
                        'Warning', body: 'Product quantity must be less than 5.'
                });
            }
        }
    };
Registries.Component.extend(ProductScreen, UpdatedProductScreen);

Nâng cấp module viin_pos để áp dụng các thay đổi. Sau khi nâng cấp, bạn khổng thể bán số lượng sản phẩm bằng hoặc lớn hơn 5. Một cảnh báo sẽ hiện ra nếu bạn cố chọn lớn hơn.

Point Of Sale Image

Hình 22.3 - Cảnh báo số lượng lớn hơn năm

Cơ chế hoạt động

Ứng dụng Điểm bán hàng cung cấp một số phương thưc extend để thực hiện các thay đổi đối với chức năng hiện có.

Trong ví dụ, chúng ta đã sửa đổi phương thức _setValue(). phương thức _setValue () của ProductScreen được gọi bất cứ khi nào người dùng thực hiện thay đổi đối với đơn đặt hàng. chúng ta định nghĩa một phương thức _setValue() mới và gọi phương thức super; điều này sẽ đảm bảo rằng bất kỳ hành động nào mà người dùng thực hiện đều được áp dụng. Sau khi gọi phương thức super, chúng ta viết logic nghiệp vụ của mình.

Ghi chú

Sử dụng super có thể phá hỏng luồng nếu không được sử dụng cẩn thận. Nếu phương thức được kế thừa từ một số tệp, bạn phải gọi phương thức super; nếu không, nó sẽ bỏ qua logic trong kế thừa tiếp theo. Điều này đôi khi dẫn đến trạng thái dữ liệu nội bộ bị hỏng.

Sửa đổi biên lai của khách hàng

Khi bạn tùy chỉnh ứng dụng Điểm bán hàng, một yêu cầu phổ biến mà bạn nhận được từ khách hàng là sửa đổi biên lai của khách hàng. Trong phần này, bạn sẽ học cách sửa đổi phiếu thu của khách hàng.

Sẵn sàng

Chúng ta sẽ thêm một dòng để cho người dùng biết họ có bao nhiêu dòng đơn hàng

Các bước thực hiện

  1. Thêm đoạn code sau vào tệp /static/src/js/viin_pos.js. Điều này sẽ thêm dữ liệu bổ sung trong môi trường biên nhận:

var models = require('point_of_sale.models');
var _super_order = models.Order.prototype;
models.Order = models.Order.extend({
    export_for_printing: function () {
        var result = _super_order.export_for_printing.apply(this, arguments);
        const order = this.pos.get_order();
        result.line_count = order.orderlines.length;
        return result;
    },
});
  1. Thêm đoạn code sau vào /static/src/xml/viin_pos.xml. Nó sẽ mở rộng mẫu biên lai mặc định:

<t t-name="OrderReceipt" t-inherit="point_of_sale.OrderReceipt" t-inherit-mode="extension" owl="1">
            <xpath expr="//div[hasclass('before-footer')]" position="before">
                    <div style="text-align:center;">
                            <div t-if="receipt.line_count">You have <t t-esc="receipt.line_count"/> lines on this order.</div>
                    </div>
            </xpath>
    </t>

Nâng cấp module viin_pos để xem các thay đổi

Point Of Sale Image

Hình 22.3 - Thay đổi mẫu biên lai