test_pos_sale_flow.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import odoo
  4. from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon
  5. from odoo.tests.common import Form
  6. @odoo.tests.tagged('post_install', '-at_install')
  7. class TestPoSSale(TestPointOfSaleHttpCommon):
  8. def test_settle_order_with_kit(self):
  9. if not self.env["ir.module.module"].search([("name", "=", "mrp"), ("state", "=", "installed")]):
  10. self.skipTest("mrp module is required for this test")
  11. self.kit = self.env['product.product'].create({
  12. 'name': 'Pizza Chicken',
  13. 'available_in_pos': True,
  14. 'type': 'product',
  15. 'lst_price': 10.0,
  16. })
  17. self.component_a = self.env['product.product'].create({
  18. 'name': 'Chicken',
  19. 'type': 'product',
  20. 'available_in_pos': True,
  21. 'uom_id': self.env.ref('uom.product_uom_gram').id,
  22. 'uom_po_id': self.env.ref('uom.product_uom_gram').id,
  23. 'lst_price': 10.0,
  24. })
  25. self.location = self.env['stock.location'].create({
  26. 'name': 'Test location',
  27. 'usage': 'internal',
  28. })
  29. self.env['stock.quant']._update_available_quantity(self.component_a, self.location, 100000)
  30. bom_product_form = Form(self.env['mrp.bom'])
  31. bom_product_form.product_id = self.kit
  32. bom_product_form.product_tmpl_id = self.kit.product_tmpl_id
  33. bom_product_form.product_qty = 1.0
  34. bom_product_form.type = 'phantom'
  35. with bom_product_form.bom_line_ids.new() as bom_line:
  36. bom_line.product_id = self.component_a
  37. bom_line.product_qty = 300.0
  38. self.bom_a = bom_product_form.save()
  39. sale_order = self.env['sale.order'].create({
  40. 'partner_id': self.env.ref('base.res_partner_2').id,
  41. 'order_line': [(0, 0, {
  42. 'product_id': self.kit.id,
  43. 'name': self.kit.name,
  44. 'product_uom_qty': 10,
  45. 'product_uom': self.kit.uom_id.id,
  46. 'price_unit': self.kit.lst_price,
  47. })],
  48. })
  49. sale_order.action_confirm()
  50. picking = sale_order.picking_ids
  51. picking.move_ids.quantity_done = 300
  52. action = picking.button_validate()
  53. wizard = Form(self.env[action['res_model']].with_context(action['context']))
  54. wizard.save().process()
  55. self.assertEqual(sale_order.order_line.qty_delivered, 1)
  56. self.main_pos_config.open_ui()
  57. self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PosSettleOrder', login="accountman")
  58. #assert that sales order qty are correctly updated
  59. self.assertEqual(sale_order.order_line.qty_delivered, 3)
  60. self.assertEqual(sale_order.picking_ids[0].move_ids.product_qty, 2100) # 7 left to deliver => 300 * 7 = 2100
  61. self.assertEqual(sale_order.picking_ids[0].move_ids.quantity_done, 0)
  62. self.assertEqual(sale_order.picking_ids[1].move_ids.product_qty, 300)
  63. self.assertEqual(sale_order.picking_ids[1].move_ids.quantity_done, 300) # 1 delivered => 300 * 2 = 600
  64. def test_settle_order_with_incompatible_partner(self):
  65. """ If the partner of the sale order is not compatible with the current pos order,
  66. then a new pos order should be to settle the newly selected sale order.
  67. """
  68. product1 = self.env['product.product'].create({
  69. 'name': 'product1',
  70. 'available_in_pos': True,
  71. 'type': 'product',
  72. 'lst_price': 10,
  73. 'taxes_id': [odoo.Command.clear()],
  74. })
  75. product2 = self.env['product.product'].create({
  76. 'name': 'product2',
  77. 'available_in_pos': True,
  78. 'type': 'product',
  79. 'lst_price': 11,
  80. 'taxes_id': [odoo.Command.clear()],
  81. })
  82. self.env['sale.order'].create({
  83. 'partner_id': self.env.ref('base.res_partner_1').id,
  84. 'partner_shipping_id': self.env.ref('base.res_partner_2').id,
  85. 'order_line': [(0, 0, {'product_id': product1.id})],
  86. })
  87. self.env['sale.order'].create({
  88. 'partner_id': self.env.ref('base.res_partner_1').id,
  89. 'partner_shipping_id': self.env.ref('base.res_partner_1').id,
  90. 'order_line': [(0, 0, {'product_id': product2.id})],
  91. })
  92. self.main_pos_config.open_ui()
  93. self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PosSettleOrderIncompatiblePartner', login="accountman")
  94. def test_settle_order_with_different_product(self):
  95. """This test create an order and settle it in the PoS. But only one of the product is delivered.
  96. And we need to make sure the quantity are correctly updated on the sale order.
  97. """
  98. #create 2 products
  99. product_a = self.env['product.product'].create({
  100. 'name': 'Product A',
  101. 'available_in_pos': True,
  102. 'type': 'product',
  103. 'lst_price': 10.0,
  104. })
  105. product_b = self.env['product.product'].create({
  106. 'name': 'Product B',
  107. 'available_in_pos': True,
  108. 'type': 'product',
  109. 'lst_price': 10.0,
  110. })
  111. #create a sale order with 2 lines
  112. sale_order = self.env['sale.order'].create({
  113. 'partner_id': self.env.ref('base.res_partner_2').id,
  114. 'order_line': [(0, 0, {
  115. 'product_id': product_a.id,
  116. 'name': product_a.name,
  117. 'product_uom_qty': 1,
  118. 'product_uom': product_a.uom_id.id,
  119. 'price_unit': product_a.lst_price,
  120. }), (0, 0, {
  121. 'product_id': product_b.id,
  122. 'name': product_b.name,
  123. 'product_uom_qty': 1,
  124. 'product_uom': product_b.uom_id.id,
  125. 'price_unit': product_b.lst_price,
  126. })],
  127. })
  128. sale_order.action_confirm()
  129. self.assertEqual(sale_order.order_line[0].qty_delivered, 0)
  130. self.assertEqual(sale_order.order_line[1].qty_delivered, 0)
  131. self.main_pos_config.open_ui()
  132. self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PosSettleOrder2', login="accountman")
  133. self.assertEqual(sale_order.order_line[0].qty_delivered, 1)
  134. self.assertEqual(sale_order.order_line[1].qty_delivered, 0)
  135. orderline_product_a = sale_order.order_line.filtered(lambda l: l.product_id.id == product_a.id)
  136. orderline_product_b = sale_order.order_line.filtered(lambda l: l.product_id.id == product_b.id)
  137. # nothing to deliver for product a because already handled in pos.
  138. self.assertEqual(orderline_product_a.move_ids.product_uom_qty, 0)
  139. # 1 item to deliver for product b.
  140. self.assertEqual(orderline_product_b.move_ids.product_uom_qty, 1)
  141. def test_settle_order_unreserve_order_lines(self):
  142. #create a product category that use the closest location for the removal strategy
  143. self.removal_strategy = self.env['product.removal'].search([('method', '=', 'closest')], limit=1)
  144. self.product_category = self.env['product.category'].create({
  145. 'name': 'Product Category',
  146. 'removal_strategy_id': self.removal_strategy.id,
  147. })
  148. self.product = self.env['product.product'].create({
  149. 'name': 'Product',
  150. 'available_in_pos': True,
  151. 'type': 'product',
  152. 'lst_price': 10.0,
  153. 'taxes_id': False,
  154. 'categ_id': self.product_category.id,
  155. })
  156. #create 2 stock location Shelf 1 and Shelf 2
  157. self.warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
  158. self.shelf_1 = self.env['stock.location'].create({
  159. 'name': 'Shelf 1',
  160. 'usage': 'internal',
  161. 'location_id': self.warehouse.lot_stock_id.id,
  162. })
  163. self.shelf_2 = self.env['stock.location'].create({
  164. 'name': 'Shelf 2',
  165. 'usage': 'internal',
  166. 'location_id': self.warehouse.lot_stock_id.id,
  167. })
  168. quants = self.env['stock.quant'].with_context(inventory_mode=True).create({
  169. 'product_id': self.product.id,
  170. 'inventory_quantity': 2,
  171. 'location_id': self.shelf_1.id,
  172. })
  173. quants |= self.env['stock.quant'].with_context(inventory_mode=True).create({
  174. 'product_id': self.product.id,
  175. 'inventory_quantity': 5,
  176. 'location_id': self.shelf_2.id,
  177. })
  178. quants.action_apply_inventory()
  179. sale_order = self.env['sale.order'].create({
  180. 'partner_id': self.env.ref('base.res_partner_2').id,
  181. 'order_line': [(0, 0, {
  182. 'product_id': self.product.id,
  183. 'name': self.product.name,
  184. 'product_uom_qty': 4,
  185. 'price_unit': self.product.lst_price,
  186. })],
  187. })
  188. sale_order.action_confirm()
  189. self.assertEqual(sale_order.order_line.move_ids.move_line_ids[0].reserved_qty, 2)
  190. self.assertEqual(sale_order.order_line.move_ids.move_line_ids[0].location_id.id, self.shelf_1.id)
  191. self.assertEqual(sale_order.order_line.move_ids.move_line_ids[1].reserved_qty, 2)
  192. self.assertEqual(sale_order.order_line.move_ids.move_line_ids[1].location_id.id, self.shelf_2.id)
  193. self.config = self.env['res.config.settings'].create({
  194. 'update_stock_quantities': 'real',
  195. })
  196. self.config.execute()
  197. self.main_pos_config.open_ui()
  198. self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PosSettleOrderRealTime', login="accountman")
  199. pos_order = self.env['pos.order'].search([], order='id desc', limit=1)
  200. self.assertEqual(pos_order.picking_ids.move_line_ids[0].qty_done, 2)
  201. self.assertEqual(pos_order.picking_ids.move_line_ids[0].location_id.id, self.shelf_1.id)
  202. self.assertEqual(pos_order.picking_ids.move_line_ids[1].qty_done, 2)
  203. self.assertEqual(pos_order.picking_ids.move_line_ids[1].location_id.id, self.shelf_2.id)
  204. self.assertEqual(sale_order.order_line.move_ids.move_lines_count, 0)
  205. def test_downpayment_refund(self):
  206. #create a sale order
  207. sale_order = self.env['sale.order'].create({
  208. 'partner_id': self.env.ref('base.res_partner_2').id,
  209. 'order_line': [(0, 0, {
  210. 'product_id': self.product_a.id,
  211. 'name': self.product_a.name,
  212. 'product_uom_qty': 1,
  213. 'price_unit': 100,
  214. 'product_uom': self.product_a.uom_id.id
  215. })],
  216. })
  217. sale_order.action_confirm()
  218. #set downpayment product in pos config
  219. self.downpayment_product = self.env['product.product'].create({
  220. 'name': 'Down Payment',
  221. 'available_in_pos': True,
  222. 'type': 'service',
  223. })
  224. self.main_pos_config.write({
  225. 'down_payment_product_id': self.downpayment_product.id,
  226. })
  227. self.main_pos_config.open_ui()
  228. self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PosRefundDownpayment', login="accountman")
  229. self.assertEqual(len(sale_order.order_line), 3)
  230. self.assertEqual(sale_order.order_line[1].qty_invoiced, 1)
  231. self.assertEqual(sale_order.order_line[2].qty_invoiced, -1)
  232. def test_settle_order_with_multistep_delivery(self):
  233. """This test create an order and settle it in the PoS. It also uses multistep delivery
  234. and we need to make sure that all the picking are cancelled if the order is fully delivered.
  235. """
  236. #get the warehouse
  237. warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
  238. warehouse.delivery_steps = 'pick_pack_ship'
  239. product_a = self.env['product.product'].create({
  240. 'name': 'Product A',
  241. 'available_in_pos': True,
  242. 'type': 'product',
  243. 'lst_price': 10.0,
  244. })
  245. #create a sale order with 2 lines
  246. sale_order = self.env['sale.order'].create({
  247. 'partner_id': self.env.ref('base.res_partner_2').id,
  248. 'order_line': [(0, 0, {
  249. 'product_id': product_a.id,
  250. 'name': product_a.name,
  251. 'product_uom_qty': 1,
  252. 'product_uom': product_a.uom_id.id,
  253. 'price_unit': product_a.lst_price,
  254. })],
  255. })
  256. sale_order.action_confirm()
  257. self.assertEqual(sale_order.order_line[0].qty_delivered, 0)
  258. self.main_pos_config.open_ui()
  259. self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PosSettleOrder3', login="accountman")
  260. self.assertEqual(sale_order.order_line[0].qty_delivered, 1)
  261. self.assertEqual(sale_order.picking_ids.mapped('state'), ['cancel', 'cancel', 'cancel'])
  262. def test_pos_not_groupable_product(self):
  263. #Create a UoM Category that is not pos_groupable
  264. uom_category = self.env['uom.category'].create({
  265. 'name': 'Test',
  266. 'is_pos_groupable': False,
  267. })
  268. uom = self.env['uom.uom'].create({
  269. 'name': 'Test',
  270. 'category_id': uom_category.id,
  271. 'uom_type': 'reference',
  272. })
  273. product_a = self.env['product.product'].create({
  274. 'name': 'Product A',
  275. 'available_in_pos': True,
  276. 'type': 'product',
  277. 'lst_price': 10.0,
  278. 'uom_id': uom.id,
  279. 'uom_po_id': uom.id,
  280. })
  281. #create a sale order with product_a
  282. self.env['sale.order'].create({
  283. 'partner_id': self.env.ref('base.res_partner_2').id,
  284. 'order_line': [(0, 0, {
  285. 'product_id': product_a.id,
  286. 'name': product_a.name,
  287. 'product_uom_qty': 3,
  288. 'product_uom': product_a.uom_id.id,
  289. 'price_unit': product_a.lst_price,
  290. })],
  291. })
  292. self.main_pos_config.open_ui()
  293. self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'PosSettleOrderNotGroupable', login="accountman")