test_purchase_invoice.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from datetime import timedelta
  4. from odoo.addons.account.tests.common import AccountTestInvoicingCommon
  5. from odoo.tests import tagged
  6. from odoo.tests.common import Form
  7. from odoo import Command, fields
  8. class TestPurchaseToInvoiceCommon(AccountTestInvoicingCommon):
  9. @classmethod
  10. def setUpClass(cls):
  11. super(TestPurchaseToInvoiceCommon, cls).setUpClass()
  12. uom_unit = cls.env.ref('uom.product_uom_unit')
  13. uom_hour = cls.env.ref('uom.product_uom_hour')
  14. cls.product_order = cls.env['product.product'].create({
  15. 'name': "Zed+ Antivirus",
  16. 'standard_price': 235.0,
  17. 'list_price': 280.0,
  18. 'type': 'consu',
  19. 'uom_id': uom_unit.id,
  20. 'uom_po_id': uom_unit.id,
  21. 'purchase_method': 'purchase',
  22. 'default_code': 'PROD_ORDER',
  23. 'taxes_id': False,
  24. })
  25. cls.service_deliver = cls.env['product.product'].create({
  26. 'name': "Cost-plus Contract",
  27. 'standard_price': 200.0,
  28. 'list_price': 180.0,
  29. 'type': 'service',
  30. 'uom_id': uom_unit.id,
  31. 'uom_po_id': uom_unit.id,
  32. 'purchase_method': 'receive',
  33. 'default_code': 'SERV_DEL',
  34. 'taxes_id': False,
  35. })
  36. cls.service_order = cls.env['product.product'].create({
  37. 'name': "Prepaid Consulting",
  38. 'standard_price': 40.0,
  39. 'list_price': 90.0,
  40. 'type': 'service',
  41. 'uom_id': uom_hour.id,
  42. 'uom_po_id': uom_hour.id,
  43. 'purchase_method': 'purchase',
  44. 'default_code': 'PRE-PAID',
  45. 'taxes_id': False,
  46. })
  47. cls.product_deliver = cls.env['product.product'].create({
  48. 'name': "Switch, 24 ports",
  49. 'standard_price': 55.0,
  50. 'list_price': 70.0,
  51. 'type': 'consu',
  52. 'uom_id': uom_unit.id,
  53. 'uom_po_id': uom_unit.id,
  54. 'purchase_method': 'receive',
  55. 'default_code': 'PROD_DEL',
  56. 'taxes_id': False,
  57. })
  58. @classmethod
  59. def init_purchase(cls, partner=None, confirm=False, products=None, taxes=None, company=False):
  60. date_planned = fields.Datetime.now() - timedelta(days=1)
  61. po_form = Form(cls.env['purchase.order'] \
  62. .with_company(company or cls.env.company) \
  63. .with_context(tracking_disable=True))
  64. po_form.partner_id = partner or cls.partner_a
  65. po_form.partner_ref = 'my_match_reference'
  66. for product in (products or []):
  67. with po_form.order_line.new() as line_form:
  68. line_form.product_id = product
  69. line_form.price_unit = product.list_price
  70. line_form.product_qty = 1
  71. line_form.product_uom = product.uom_id
  72. line_form.date_planned = date_planned
  73. if taxes:
  74. line_form.tax_ids.clear()
  75. for tax in taxes:
  76. line_form.tax_ids.add(tax)
  77. rslt = po_form.save()
  78. if confirm:
  79. rslt.button_confirm()
  80. return rslt
  81. @tagged('post_install', '-at_install')
  82. class TestPurchaseToInvoice(TestPurchaseToInvoiceCommon):
  83. def test_vendor_bill_delivered(self):
  84. """Test if a order of product invoiced by delivered quantity can be
  85. correctly invoiced."""
  86. purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
  87. 'partner_id': self.partner_a.id,
  88. })
  89. PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
  90. pol_prod_deliver = PurchaseOrderLine.create({
  91. 'name': self.product_deliver.name,
  92. 'product_id': self.product_deliver.id,
  93. 'product_qty': 10.0,
  94. 'product_uom': self.product_deliver.uom_id.id,
  95. 'price_unit': self.product_deliver.list_price,
  96. 'order_id': purchase_order.id,
  97. 'taxes_id': False,
  98. })
  99. pol_serv_deliver = PurchaseOrderLine.create({
  100. 'name': self.service_deliver.name,
  101. 'product_id': self.service_deliver.id,
  102. 'product_qty': 10.0,
  103. 'product_uom': self.service_deliver.uom_id.id,
  104. 'price_unit': self.service_deliver.list_price,
  105. 'order_id': purchase_order.id,
  106. 'taxes_id': False,
  107. })
  108. purchase_order.button_confirm()
  109. self.assertEqual(purchase_order.invoice_status, "no")
  110. for line in purchase_order.order_line:
  111. self.assertEqual(line.qty_to_invoice, 0.0)
  112. self.assertEqual(line.qty_invoiced, 0.0)
  113. purchase_order.order_line.qty_received = 5
  114. self.assertEqual(purchase_order.invoice_status, "to invoice")
  115. for line in purchase_order.order_line:
  116. self.assertEqual(line.qty_to_invoice, 5)
  117. self.assertEqual(line.qty_invoiced, 0.0)
  118. purchase_order.action_create_invoice()
  119. self.assertEqual(purchase_order.invoice_status, "invoiced")
  120. for line in purchase_order.order_line:
  121. self.assertEqual(line.qty_to_invoice, 0.0)
  122. self.assertEqual(line.qty_invoiced, 5)
  123. def test_vendor_bill_ordered(self):
  124. """Test if a order of product invoiced by ordered quantity can be
  125. correctly invoiced."""
  126. purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
  127. 'partner_id': self.partner_a.id,
  128. })
  129. PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
  130. pol_prod_order = PurchaseOrderLine.create({
  131. 'name': self.product_order.name,
  132. 'product_id': self.product_order.id,
  133. 'product_qty': 10.0,
  134. 'product_uom': self.product_order.uom_id.id,
  135. 'price_unit': self.product_order.list_price,
  136. 'order_id': purchase_order.id,
  137. 'taxes_id': False,
  138. })
  139. pol_serv_order = PurchaseOrderLine.create({
  140. 'name': self.service_order.name,
  141. 'product_id': self.service_order.id,
  142. 'product_qty': 10.0,
  143. 'product_uom': self.service_order.uom_id.id,
  144. 'price_unit': self.service_order.list_price,
  145. 'order_id': purchase_order.id,
  146. 'taxes_id': False,
  147. })
  148. purchase_order.button_confirm()
  149. self.assertEqual(purchase_order.invoice_status, "to invoice")
  150. for line in purchase_order.order_line:
  151. self.assertEqual(line.qty_to_invoice, 10)
  152. self.assertEqual(line.qty_invoiced, 0.0)
  153. purchase_order.order_line.qty_received = 5
  154. self.assertEqual(purchase_order.invoice_status, "to invoice")
  155. for line in purchase_order.order_line:
  156. self.assertEqual(line.qty_to_invoice, 10)
  157. self.assertEqual(line.qty_invoiced, 0.0)
  158. purchase_order.action_create_invoice()
  159. self.assertEqual(purchase_order.invoice_status, "invoiced")
  160. for line in purchase_order.order_line:
  161. self.assertEqual(line.qty_to_invoice, 0.0)
  162. self.assertEqual(line.qty_invoiced, 10)
  163. def test_vendor_bill_delivered_return(self):
  164. """Test when return product, a order of product invoiced by delivered
  165. quantity can be correctly invoiced."""
  166. purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
  167. 'partner_id': self.partner_a.id,
  168. })
  169. PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
  170. pol_prod_deliver = PurchaseOrderLine.create({
  171. 'name': self.product_deliver.name,
  172. 'product_id': self.product_deliver.id,
  173. 'product_qty': 10.0,
  174. 'product_uom': self.product_deliver.uom_id.id,
  175. 'price_unit': self.product_deliver.list_price,
  176. 'order_id': purchase_order.id,
  177. 'taxes_id': False,
  178. })
  179. pol_serv_deliver = PurchaseOrderLine.create({
  180. 'name': self.service_deliver.name,
  181. 'product_id': self.service_deliver.id,
  182. 'product_qty': 10.0,
  183. 'product_uom': self.service_deliver.uom_id.id,
  184. 'price_unit': self.service_deliver.list_price,
  185. 'order_id': purchase_order.id,
  186. 'taxes_id': False,
  187. })
  188. purchase_order.button_confirm()
  189. purchase_order.order_line.qty_received = 10
  190. purchase_order.action_create_invoice()
  191. self.assertEqual(purchase_order.invoice_status, "invoiced")
  192. for line in purchase_order.order_line:
  193. self.assertEqual(line.qty_to_invoice, 0.0)
  194. self.assertEqual(line.qty_invoiced, 10)
  195. purchase_order.order_line.qty_received = 5
  196. self.assertEqual(purchase_order.invoice_status, "to invoice")
  197. for line in purchase_order.order_line:
  198. self.assertEqual(line.qty_to_invoice, -5)
  199. self.assertEqual(line.qty_invoiced, 10)
  200. purchase_order.action_create_invoice()
  201. self.assertEqual(purchase_order.invoice_status, "invoiced")
  202. for line in purchase_order.order_line:
  203. self.assertEqual(line.qty_to_invoice, 0.0)
  204. self.assertEqual(line.qty_invoiced, 5)
  205. def test_vendor_bill_ordered_return(self):
  206. """Test when return product, a order of product invoiced by ordered
  207. quantity can be correctly invoiced."""
  208. purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
  209. 'partner_id': self.partner_a.id,
  210. })
  211. PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
  212. pol_prod_order = PurchaseOrderLine.create({
  213. 'name': self.product_order.name,
  214. 'product_id': self.product_order.id,
  215. 'product_qty': 10.0,
  216. 'product_uom': self.product_order.uom_id.id,
  217. 'price_unit': self.product_order.list_price,
  218. 'order_id': purchase_order.id,
  219. 'taxes_id': False,
  220. })
  221. pol_serv_order = PurchaseOrderLine.create({
  222. 'name': self.service_order.name,
  223. 'product_id': self.service_order.id,
  224. 'product_qty': 10.0,
  225. 'product_uom': self.service_order.uom_id.id,
  226. 'price_unit': self.service_order.list_price,
  227. 'order_id': purchase_order.id,
  228. 'taxes_id': False,
  229. })
  230. purchase_order.button_confirm()
  231. purchase_order.order_line.qty_received = 10
  232. purchase_order.action_create_invoice()
  233. self.assertEqual(purchase_order.invoice_status, "invoiced")
  234. for line in purchase_order.order_line:
  235. self.assertEqual(line.qty_to_invoice, 0.0)
  236. self.assertEqual(line.qty_invoiced, 10)
  237. purchase_order.order_line.qty_received = 5
  238. self.assertEqual(purchase_order.invoice_status, "invoiced")
  239. for line in purchase_order.order_line:
  240. self.assertEqual(line.qty_to_invoice, 0.0)
  241. self.assertEqual(line.qty_invoiced, 10)
  242. def test_vendor_severals_bills_and_multicurrency(self):
  243. """
  244. This test ensures that, when adding several PO to a bill, if they are expressed with different
  245. currency, the amount of each AML is converted to the bill's currency
  246. """
  247. PurchaseOrderLine = self.env['purchase.order.line']
  248. PurchaseBillUnion = self.env['purchase.bill.union']
  249. ResCurrencyRate = self.env['res.currency.rate']
  250. usd = self.env.ref('base.USD')
  251. eur = self.env.ref('base.EUR')
  252. purchase_orders = []
  253. ResCurrencyRate.create({'currency_id': usd.id, 'rate': 1})
  254. ResCurrencyRate.create({'currency_id': eur.id, 'rate': 2})
  255. for currency in [usd, eur]:
  256. po = self.env['purchase.order'].with_context(tracking_disable=True).create({
  257. 'partner_id': self.partner_a.id,
  258. 'currency_id': currency.id,
  259. })
  260. pol_prod_order = PurchaseOrderLine.create({
  261. 'name': self.product_order.name,
  262. 'product_id': self.product_order.id,
  263. 'product_qty': 1,
  264. 'product_uom': self.product_order.uom_id.id,
  265. 'price_unit': 1000,
  266. 'order_id': po.id,
  267. 'taxes_id': False,
  268. })
  269. po.button_confirm()
  270. pol_prod_order.write({'qty_received': 1})
  271. purchase_orders.append(po)
  272. move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
  273. move_form.purchase_vendor_bill_id = PurchaseBillUnion.browse(-purchase_orders[0].id)
  274. move_form.purchase_vendor_bill_id = PurchaseBillUnion.browse(-purchase_orders[1].id)
  275. move = move_form.save()
  276. self.assertInvoiceValues(move, [
  277. {
  278. 'display_type': 'product',
  279. 'amount_currency': 1000,
  280. 'balance': 1000,
  281. }, {
  282. 'display_type': 'product',
  283. 'amount_currency': 500,
  284. 'balance': 500,
  285. }, {
  286. 'display_type': 'payment_term',
  287. 'amount_currency': -1500,
  288. 'balance': -1500,
  289. },
  290. ], {
  291. 'amount_total': 1500,
  292. 'currency_id': usd.id,
  293. })
  294. def test_product_price_decimal_accuracy(self):
  295. self.env.ref('product.decimal_price').digits = 3
  296. self.env.company.currency_id.rounding = 0.01
  297. po = self.env['purchase.order'].with_context(tracking_disable=True).create({
  298. 'partner_id': self.partner_a.id,
  299. 'order_line': [(0, 0, {
  300. 'name': self.product_a.name,
  301. 'product_id': self.product_a.id,
  302. 'product_qty': 12,
  303. 'product_uom': self.product_a.uom_id.id,
  304. 'price_unit': 0.001,
  305. 'taxes_id': False,
  306. })]
  307. })
  308. po.button_confirm()
  309. po.order_line.qty_received = 12
  310. move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
  311. move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po.id)
  312. move = move_form.save()
  313. self.assertEqual(move.amount_total, 0.01)
  314. def test_vendor_bill_analytic_account_model_change(self):
  315. """ Tests whether, when an analytic account rule is set, and user changes manually the analytic account on
  316. the po, it is the same that is mentioned in the bill.
  317. """
  318. # Required for `analytic.group_analytic_accounting` to be visible in the view
  319. self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
  320. analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
  321. analytic_account_default = self.env['account.analytic.account'].create({'name': 'default', 'plan_id': analytic_plan.id})
  322. analytic_account_manual = self.env['account.analytic.account'].create({'name': 'manual', 'plan_id': analytic_plan.id})
  323. self.env['account.analytic.distribution.model'].create({
  324. 'analytic_distribution': {analytic_account_default.id: 100},
  325. 'product_id': self.product_order.id,
  326. })
  327. analytic_distribution_manual = {str(analytic_account_manual.id): 100}
  328. po_form = Form(self.env['purchase.order'].with_context(tracking_disable=True))
  329. po_form.partner_id = self.partner_a
  330. with po_form.order_line.new() as po_line_form:
  331. po_line_form.name = self.product_order.name
  332. po_line_form.product_id = self.product_order
  333. po_line_form.product_qty = 1.0
  334. po_line_form.price_unit = 10
  335. po_line_form.analytic_distribution = analytic_distribution_manual
  336. purchase_order = po_form.save()
  337. purchase_order.button_confirm()
  338. purchase_order.action_create_invoice()
  339. aml = self.env['account.move.line'].search([('purchase_line_id', '=', purchase_order.order_line.id)])
  340. self.assertRecordValues(aml, [{'analytic_distribution': analytic_distribution_manual}])
  341. def test_purchase_order_analytic_account_product_change(self):
  342. self.env.user.groups_id += self.env.ref('account.group_account_readonly')
  343. self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
  344. analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
  345. analytic_account_super = self.env['account.analytic.account'].create({'name': 'Super Account', 'plan_id': analytic_plan.id})
  346. analytic_account_great = self.env['account.analytic.account'].create({'name': 'Great Account', 'plan_id': analytic_plan.id})
  347. super_product = self.env['product.product'].create({'name': 'Super Product'})
  348. great_product = self.env['product.product'].create({'name': 'Great Product'})
  349. self.env['account.analytic.distribution.model'].create([
  350. {
  351. 'analytic_distribution': {analytic_account_super.id: 100},
  352. 'product_id': super_product.id,
  353. },
  354. {
  355. 'analytic_distribution': {analytic_account_great.id: 100},
  356. 'product_id': great_product.id,
  357. },
  358. ])
  359. po_form = Form(self.env['purchase.order'].with_context(tracking_disable=True))
  360. po_form.partner_id = self.env.ref('base.res_partner_1')
  361. with po_form.order_line.new() as po_line_form:
  362. po_line_form.name = super_product.name
  363. po_line_form.product_id = super_product
  364. purchase_order = po_form.save()
  365. purchase_order_line = purchase_order.order_line
  366. self.assertEqual(purchase_order_line.analytic_distribution, {str(analytic_account_super.id): 100}, "The analytic account should be set to 'Super Account'")
  367. purchase_order_line.write({'product_id': great_product.id})
  368. self.assertEqual(purchase_order_line.analytic_distribution, {str(analytic_account_great.id): 100}, "The analytic account should be set to 'Great Account'")
  369. po_no_analytic_distribution = self.env['purchase.order'].create({
  370. 'partner_id': self.env.ref('base.res_partner_1').id,
  371. })
  372. pol_no_analytic_distribution = self.env['purchase.order.line'].create({
  373. 'name': super_product.name,
  374. 'product_id': super_product.id,
  375. 'order_id': po_no_analytic_distribution.id,
  376. 'analytic_distribution': False,
  377. })
  378. po_no_analytic_distribution.button_confirm()
  379. self.assertFalse(pol_no_analytic_distribution.analytic_distribution, "The compute should not overwrite what the user has set.")
  380. def test_purchase_order_to_invoice_analytic_rule_with_account_prefix(self):
  381. """
  382. Test whether, when an analytic plan is set within the scope (applicability) of purchase
  383. and with an account prefix set in the distribution model,
  384. the default analytic account is correctly set during the conversion from po to invoice
  385. """
  386. self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
  387. analytic_plan_default = self.env['account.analytic.plan'].create({
  388. 'name': 'default',
  389. 'applicability_ids': [Command.create({
  390. 'business_domain': 'bill',
  391. 'applicability': 'optional',
  392. })]
  393. })
  394. analytic_account_default = self.env['account.analytic.account'].create({'name': 'default', 'plan_id': analytic_plan_default.id})
  395. analytic_distribution_model = self.env['account.analytic.distribution.model'].create({
  396. 'account_prefix': '600',
  397. 'analytic_distribution': {analytic_account_default.id: 100},
  398. 'product_id': self.product_a.id,
  399. })
  400. po = self.env['purchase.order'].create({'partner_id': self.partner_a.id})
  401. self.env['purchase.order.line'].create({
  402. 'order_id': po.id,
  403. 'name': 'test',
  404. 'product_id': self.product_a.id
  405. })
  406. self.assertFalse(po.order_line.analytic_distribution, "There should be no analytic set.")
  407. po.button_confirm()
  408. po.order_line.qty_received = 1
  409. po.action_create_invoice()
  410. self.assertRecordValues(po.invoice_ids.invoice_line_ids,
  411. [{'analytic_distribution': analytic_distribution_model.analytic_distribution}])
  412. def test_sequence_invoice_lines_from_multiple_purchases(self):
  413. """Test if the invoice lines are sequenced by purchase order when creating an invoice
  414. from multiple selected po's"""
  415. purchase_orders = self.env['purchase.order']
  416. for _ in range(3):
  417. pol_vals = [
  418. (0, 0, {
  419. 'name': self.product_order.name,
  420. 'product_id': self.product_order.id,
  421. 'product_qty': 10.0,
  422. 'product_uom': self.product_order.uom_id.id,
  423. 'price_unit': self.product_order.list_price,
  424. 'taxes_id': False,
  425. 'sequence': sequence_number,
  426. }) for sequence_number in range(10, 13)]
  427. purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
  428. 'partner_id': self.partner_a.id,
  429. 'order_line': pol_vals,
  430. })
  431. purchase_order.button_confirm()
  432. purchase_orders |= purchase_order
  433. action = purchase_orders.action_create_invoice()
  434. invoice = self.env['account.move'].browse(action['res_id'])
  435. expected_purchase = [
  436. purchase_orders[0], purchase_orders[0], purchase_orders[0],
  437. purchase_orders[1], purchase_orders[1], purchase_orders[1],
  438. purchase_orders[2], purchase_orders[2], purchase_orders[2],
  439. ]
  440. for line in invoice.invoice_line_ids.sorted('sequence'):
  441. self.assertEqual(line.purchase_order_id, expected_purchase.pop(0))
  442. def test_sequence_autocomplete_invoice(self):
  443. """Test if the invoice lines are sequenced by purchase order when using the autocomplete
  444. feature on a bill to add lines from po's"""
  445. purchase_orders = self.env['purchase.order']
  446. for _ in range(3):
  447. pol_vals = [
  448. (0, 0, {
  449. 'name': self.product_order.name,
  450. 'product_id': self.product_order.id,
  451. 'product_qty': 10.0,
  452. 'product_uom': self.product_order.uom_id.id,
  453. 'price_unit': self.product_order.list_price,
  454. 'taxes_id': False,
  455. 'sequence': sequence_number,
  456. }) for sequence_number in range(10, 13)]
  457. purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
  458. 'partner_id': self.partner_a.id,
  459. 'order_line': pol_vals,
  460. })
  461. purchase_order.button_confirm()
  462. purchase_orders |= purchase_order
  463. move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
  464. PurchaseBillUnion = self.env['purchase.bill.union']
  465. move_form.purchase_vendor_bill_id = PurchaseBillUnion.browse(-purchase_orders[0].id)
  466. move_form.purchase_vendor_bill_id = PurchaseBillUnion.browse(-purchase_orders[1].id)
  467. move_form.purchase_vendor_bill_id = PurchaseBillUnion.browse(-purchase_orders[2].id)
  468. invoice = move_form.save()
  469. expected_purchase = [
  470. purchase_orders[0], purchase_orders[0], purchase_orders[0],
  471. purchase_orders[1], purchase_orders[1], purchase_orders[1],
  472. purchase_orders[2], purchase_orders[2], purchase_orders[2],
  473. ]
  474. for line in invoice.invoice_line_ids.sorted('sequence'):
  475. self.assertEqual(line.purchase_order_id, expected_purchase.pop(0))
  476. def test_partial_billing_interaction_with_invoicing_switch_threshold(self):
  477. """ Let's say you create a partial bill 'B' for a given PO. Now if you change the
  478. 'Invoicing Switch Threshold' such that the bill date of 'B' is before the new threshold,
  479. the PO should still take bill 'B' into account.
  480. """
  481. if not self.env['ir.module.module'].search([('name', '=', 'account_accountant'), ('state', '=', 'installed')]):
  482. self.skipTest("This test requires the installation of the account_account module")
  483. purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
  484. 'partner_id': self.partner_a.id,
  485. 'order_line': [
  486. Command.create({
  487. 'name': self.product_deliver.name,
  488. 'product_id': self.product_deliver.id,
  489. 'product_qty': 20.0,
  490. 'product_uom': self.product_deliver.uom_id.id,
  491. 'price_unit': self.product_deliver.list_price,
  492. 'taxes_id': False,
  493. }),
  494. ],
  495. })
  496. line = purchase_order.order_line[0]
  497. purchase_order.button_confirm()
  498. line.qty_received = 10
  499. purchase_order.action_create_invoice()
  500. invoice = purchase_order.invoice_ids
  501. invoice.invoice_date = invoice.date
  502. invoice.action_post()
  503. self.assertEqual(line.qty_invoiced, 10)
  504. self.env['res.config.settings'].create({
  505. 'invoicing_switch_threshold': fields.Date.add(invoice.invoice_date, days=30),
  506. }).execute()
  507. invoice.invalidate_model(fnames=['payment_state'])
  508. self.assertEqual(line.qty_invoiced, 10)
  509. line.qty_received = 15
  510. self.assertEqual(line.qty_invoiced, 10)
  511. def test_on_change_quantity_price_unit(self):
  512. """ When a user changes the quantity of a product in a purchase order it
  513. should only update the unit price if PO line has no invoice line. """
  514. supplierinfo_vals = {
  515. 'partner_id': self.partner_a.id,
  516. 'price': 10.0,
  517. 'min_qty': 1,
  518. "product_id": self.product_order.id,
  519. "product_tmpl_id": self.product_order.product_tmpl_id.id,
  520. }
  521. supplierinfo = self.env["product.supplierinfo"].create(supplierinfo_vals)
  522. po_form = Form(self.env['purchase.order'])
  523. po_form.partner_id = self.partner_a
  524. with po_form.order_line.new() as po_line_form:
  525. po_line_form.product_id = self.product_order
  526. po_line_form.product_qty = 1
  527. po = po_form.save()
  528. po_line = po.order_line[0]
  529. self.assertEqual(10.0, po_line.price_unit, "Unit price should be set to 10.0 for 1 quantity")
  530. # Ensure price unit is updated when changing quantity on a un-confirmed PO
  531. supplierinfo.write({'min_qty': 2, 'price': 20.0})
  532. po_line.write({'product_qty': 2})
  533. self.assertEqual(20.0, po_line.price_unit, "Unit price should be set to 20.0 for 2 quantity")
  534. po.button_confirm()
  535. # Ensure price unit is updated when changing quantity on a confirmed PO
  536. supplierinfo.write({'min_qty': 3, 'price': 30.0})
  537. po_line.write({'product_qty': 3})
  538. self.assertEqual(30.0, po_line.price_unit, "Unit price should be set to 30.0 for 3 quantity")
  539. po.action_create_invoice()
  540. # Ensure price unit is NOT updated when changing quantity on PO confirmed and line linked to an invoice line
  541. supplierinfo.write({'min_qty': 4, 'price': 40.0})
  542. po_line.write({'product_qty': 4})
  543. self.assertEqual(30.0, po_line.price_unit, "Unit price should be set to 30.0 for 3 quantity")
  544. with po_form.order_line.new() as po_line_form:
  545. po_line_form.product_id = self.product_order
  546. po_line_form.product_qty = 1
  547. po = po_form.save()
  548. po_line = po.order_line[1]
  549. self.assertEqual(235.0, po_line.price_unit, "Unit price should be reset to 235.0 since the supplier supplies minimum of 4 quantities")
  550. # Ensure price unit is updated when changing quantity on PO confirmed and line NOT linked to an invoice line
  551. po_line.write({'product_qty': 4})
  552. self.assertEqual(40.0, po_line.price_unit, "Unit price should be set to 40.0 for 4 quantity")
  553. @tagged('post_install', '-at_install')
  554. class TestInvoicePurchaseMatch(TestPurchaseToInvoiceCommon):
  555. def test_total_match_via_partner(self):
  556. po = self.init_purchase(confirm=True, partner=self.partner_a, products=[self.product_order])
  557. invoice = self.init_invoice('in_invoice', partner=self.partner_a, products=[self.product_order])
  558. invoice._find_and_set_purchase_orders(
  559. [], invoice.partner_id.id, invoice.amount_total)
  560. self.assertTrue(invoice.id in po.invoice_ids.ids)
  561. self.assertEqual(invoice.amount_total, po.amount_total)
  562. def test_total_match_via_po_reference(self):
  563. po = self.init_purchase(confirm=True, products=[self.product_order])
  564. invoice = self.init_invoice('in_invoice', partner=self.partner_a, products=[self.product_order])
  565. invoice._find_and_set_purchase_orders(
  566. ['my_match_reference'], invoice.partner_id.id, invoice.amount_total)
  567. self.assertTrue(invoice.id in po.invoice_ids.ids)
  568. self.assertEqual(invoice.amount_total, po.amount_total)
  569. def test_subset_total_match_prefer_purchase(self):
  570. po = self.init_purchase(confirm=True, products=[self.product_order, self.service_order])
  571. invoice = self.init_invoice('in_invoice', partner=self.partner_a, products=[self.product_order])
  572. invoice._find_and_set_purchase_orders(
  573. ['my_match_reference'], invoice.partner_id.id, invoice.amount_total, prefer_purchase_line=True)
  574. additional_unmatch_po_line = po.order_line.filtered(lambda l: l.product_id == self.service_order)
  575. self.assertTrue(invoice.id in po.invoice_ids.ids)
  576. self.assertTrue(additional_unmatch_po_line.id in invoice.line_ids.purchase_line_id.ids)
  577. self.assertTrue(invoice.line_ids.filtered(lambda l: l.purchase_line_id == additional_unmatch_po_line).quantity == 0)
  578. def test_subset_total_match_reject_purchase(self):
  579. po = self.init_purchase(confirm=True, products=[self.product_order, self.service_order])
  580. invoice = self.init_invoice('in_invoice', partner=self.partner_a, products=[self.product_order])
  581. invoice._find_and_set_purchase_orders(
  582. ['my_match_reference'], invoice.partner_id.id, invoice.amount_total, prefer_purchase_line=False)
  583. additional_unmatch_po_line = po.order_line.filtered(lambda l: l.product_id == self.service_order)
  584. self.assertTrue(invoice.id in po.invoice_ids.ids)
  585. self.assertTrue(additional_unmatch_po_line.id not in invoice.line_ids.purchase_line_id.ids)
  586. def test_po_match_prefer_purchase(self):
  587. po = self.init_purchase(confirm=True, products=[self.product_order, self.service_order])
  588. invoice = self.init_invoice('in_invoice', products=[self.product_a])
  589. invoice._find_and_set_purchase_orders(
  590. ['my_match_reference'], invoice.partner_id.id, invoice.amount_total, prefer_purchase_line=True)
  591. self.assertTrue(invoice.id in po.invoice_ids.ids)
  592. def test_po_match_reject_purchase(self):
  593. po = self.init_purchase(confirm=True, products=[self.product_order, self.service_order])
  594. invoice = self.init_invoice('in_invoice', products=[self.product_a])
  595. invoice._find_and_set_purchase_orders(
  596. ['my_match_reference'], invoice.partner_id.id, invoice.amount_total, prefer_purchase_line=False)
  597. self.assertTrue(invoice.id not in po.invoice_ids.ids)
  598. self.assertNotEqual(invoice.amount_total, po.amount_total)
  599. def test_no_match(self):
  600. po = self.init_purchase(confirm=True, products=[self.product_order, self.service_order])
  601. invoice = self.init_invoice('in_invoice', products=[self.product_a])
  602. invoice._find_and_set_purchase_orders(
  603. ['other_reference'], invoice.partner_id.id, invoice.amount_total, prefer_purchase_line=False)
  604. self.assertTrue(invoice.id not in po.invoice_ids.ids)
  605. def test_onchange_partner_currency(self):
  606. """
  607. Test that the currency of the Bill is correctly set when the partner is changed
  608. as well as the currency of the Bill lines
  609. """
  610. vendor_eur = self.env['res.partner'].create({
  611. 'name': 'Vendor EUR',
  612. 'property_purchase_currency_id': self.env.ref('base.EUR').id,
  613. })
  614. vendor_us = self.env['res.partner'].create({
  615. 'name': 'Vendor USD',
  616. 'property_purchase_currency_id': self.env.ref('base.USD').id,
  617. })
  618. vendor_no_currency = self.env['res.partner'].create({
  619. 'name': 'Vendor No Currency',
  620. })
  621. move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
  622. move_form.partner_id = vendor_eur
  623. with move_form.invoice_line_ids.new() as line_form:
  624. line_form.product_id = self.product_order
  625. line_form.quantity = 1
  626. bill = move_form.save()
  627. self.assertEqual(bill.currency_id, self.env.ref('base.EUR'), "The currency of the Bill should be the same as the currency of the partner")
  628. self.assertEqual(bill.invoice_line_ids.currency_id, self.env.ref('base.EUR'), "The currency of the Bill lines should be the same as the currency of the partner")
  629. move_form.partner_id = vendor_us
  630. bill = move_form.save()
  631. self.assertEqual(bill.currency_id, self.env.ref('base.USD'), "The currency of the Bill should be the same as the currency of the partner")
  632. self.assertEqual(bill.invoice_line_ids.currency_id, self.env.ref('base.USD'), "The currency of the Bill lines should be the same as the currency of the partner")
  633. move_form.partner_id = vendor_no_currency
  634. bill = move_form.save()
  635. self.assertEqual(bill.currency_id, self.env.company.currency_id, "The currency of the Bill should be the same as the currency of the company")
  636. self.assertEqual(bill.invoice_line_ids.currency_id, self.env.company.currency_id, "The currency of the Bill lines should be the same as the currency of the company")
  637. def test_onchange_partner_no_currency(self):
  638. """
  639. Test that the currency of the Bill is correctly set when the partner is changed
  640. as well as the currency of the Bill lines even if the partner has no property_purchase_currency_id set
  641. or when and the `default_currency_id` is defined in the context
  642. """
  643. vendor_a = self.env['res.partner'].create({
  644. 'name': 'Vendor A with No Currency',
  645. })
  646. vendor_b = self.env['res.partner'].create({
  647. 'name': 'Vendor B with No Currency',
  648. })
  649. ctx = {'default_move_type': 'in_invoice'}
  650. move_form = Form(self.env['account.move'].with_context(ctx))
  651. move_form.partner_id = vendor_a
  652. move_form.currency_id = self.env.ref('base.EUR')
  653. with move_form.invoice_line_ids.new() as line_form:
  654. line_form.product_id = self.product_order
  655. line_form.quantity = 1
  656. bill = move_form.save()
  657. self.assertEqual(bill.currency_id, self.env.ref('base.EUR'), "The currency of the Bill should be the one set on the Bill")
  658. self.assertEqual(bill.invoice_line_ids.currency_id, self.env.ref('base.EUR'), "The currency of the Bill lines should be the same as the currency of the Bill")
  659. move_form.partner_id = vendor_b
  660. bill = move_form.save()
  661. self.assertEqual(bill.currency_id, self.env.ref('base.EUR'), "The currency of the Bill should be the one set on the Bill")
  662. self.assertEqual(bill.invoice_line_ids.currency_id, self.env.ref('base.EUR'), "The currency of the Bill lines should be the same as the currency of the Bill")
  663. ctx['default_currency_id'] = self.currency_data['currency'].id
  664. move_form_currency_in_context = Form(self.env['account.move'].with_context(ctx))
  665. move_form_currency_in_context.currency_id = self.env.ref('base.EUR')
  666. with move_form_currency_in_context.invoice_line_ids.new() as line_form:
  667. line_form.product_id = self.product_order
  668. line_form.quantity = 1
  669. move_form_currency_in_context.save()
  670. move_form_currency_in_context.partner_id = vendor_a
  671. bill = move_form_currency_in_context.save()
  672. self.assertEqual(bill.currency_id, self.currency_data['currency'], "The currency of the Bill should be the one of the context")
  673. self.assertEqual(bill.invoice_line_ids.currency_id, self.currency_data['currency'], "The currency of the Bill lines should be the same as the currency of the Bill")