diff --git a/modules/purchase/CHANGELOG b/modules/purchase/CHANGELOG index bef9573ea4..1b283f99dc 100644 --- a/modules/purchase/CHANGELOG +++ b/modules/purchase/CHANGELOG @@ -1,3 +1,4 @@ +* Add a button to create invoices manually Version 6.8.0 - 2023-05-01 -------------------------- diff --git a/modules/purchase/purchase.py b/modules/purchase/purchase.py index 1906e91fde..ed84941748 100644 --- a/modules/purchase/purchase.py +++ b/modules/purchase/purchase.py @@ -306,6 +306,12 @@ class Purchase( 'tryton-forward', 'tryton-refresh'), 'depends': ['state'], }, + 'manual_invoice': { + 'invisible': ( + (Eval('invoice_method') != 'manual') + | ~Eval('state').in_(['processing', 'done'])), + 'depends': ['invoice_method', 'state'], + }, 'handle_invoice_exception': { 'invisible': ((Eval('invoice_state') != 'exception') | (Eval('state') == 'cancelled')), @@ -763,8 +769,9 @@ class Purchase( def create_invoice(self): 'Create an invoice for the purchase and return it' - - if self.invoice_method == 'manual': + context = Transaction().context + if (self.invoice_method == 'manual' + and not context.get('_purchase_manual_invoice', False)): return invoice_lines = [] @@ -998,6 +1005,13 @@ class Purchase( if done: cls.do(done) + @classmethod + @ModelView.button + def manual_invoice(cls, purchases): + purchases = [p for p in purchases if p.invoice_method == 'manual'] + with Transaction().set_context(_purchase_manual_invoice=True): + cls.process(purchases) + @classmethod @ModelView.button_action('purchase.wizard_modify_header') def modify_header(cls, purchases): @@ -1670,7 +1684,8 @@ class Line(sequence_ordered(), ModelSQL, ModelView): def _get_invoice_not_line(self): 'Return if the not line should be invoiced' - return (self.purchase.invoice_method == 'order' + return ( + self.purchase.invoice_method in {'order', 'manual'} and not self.invoice_lines) def _get_invoice_line_quantity(self): @@ -1678,7 +1693,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView): pool = Pool() Uom = pool.get('product.uom') - if (self.purchase.invoice_method == 'order' + if (self.purchase.invoice_method in {'order', 'manual'} or not self.product or self.product.type == 'service'): return self.quantity @@ -1717,7 +1732,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView): def _get_invoice_line_moves(self): 'Return the stock moves that should be invoiced' moves = [] - if self.purchase.invoice_method == 'order': + if self.purchase.invoice_method in {'order', 'manual'}: moves.extend(self.moves) elif self.purchase.invoice_method == 'shipment': for move in self.moves: @@ -1790,7 +1805,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView): def _get_move_invoice_lines(self, move_type): 'Return the invoice lines that should be shipped' - if self.purchase.invoice_method == 'order': + if self.purchase.invoice_method in {'order', 'manual'}: return [l for l in self.invoice_lines] else: return [l for l in self.invoice_lines if not l.stock_moves] diff --git a/modules/purchase/purchase.xml b/modules/purchase/purchase.xml index 56e8f2db2a..a4404a2530 100644 --- a/modules/purchase/purchase.xml +++ b/modules/purchase/purchase.xml @@ -344,6 +344,20 @@ this repository contains the full copyright notices and license terms. --> + + manual_invoice + Create Invoice + + + + + + + + + + + modify_header Modify Header diff --git a/modules/purchase/tests/scenario_purchase_manual_invoice.rst b/modules/purchase/tests/scenario_purchase_manual_invoice.rst new file mode 100644 index 0000000000..bd0882625f --- /dev/null +++ b/modules/purchase/tests/scenario_purchase_manual_invoice.rst @@ -0,0 +1,97 @@ +================================ +Purchase Manual Invoice Scenario +================================ + +Imports:: + + >>> from decimal import Decimal + + >>> from proteus import Model + >>> from trytond.tests.tools import activate_modules + >>> from trytond.modules.company.tests.tools import create_company + >>> from trytond.modules.account.tests.tools import ( + ... create_chart, get_accounts) + +Activate modules:: + + >>> config = activate_modules('purchase') + + >>> Party = Model.get('party.party') + >>> ProductCategory = Model.get('product.category') + >>> ProductTemplate = Model.get('product.template') + >>> ProductUom = Model.get('product.uom') + >>> Purchase = Model.get('purchase.purchase') + +Create company:: + + >>> _ = create_company() + +Create chart of accounts:: + + >>> _ = create_chart() + >>> accounts = get_accounts() + +Create party:: + + >>> supplier = Party(name='Supplier') + >>> supplier.save() + +Create account category:: + + >>> account_category = ProductCategory(name="Account Category") + >>> account_category.accounting = True + >>> account_category.account_expense = accounts['expense'] + >>> account_category.save() + +Create product:: + + >>> unit, = ProductUom.find([('name', '=', 'Unit')]) + + >>> template = ProductTemplate() + >>> template.name = 'product' + >>> template.default_uom = unit + >>> template.type = 'service' + >>> template.purchasable = True + >>> template.account_category = account_category + >>> product, = template.products + >>> product.cost_price = Decimal('5') + >>> template.save() + >>> product, = template.products + +Purchase with manual invoice method:: + + >>> purchase = Purchase() + >>> purchase.party = supplier + >>> purchase.invoice_method = 'manual' + >>> line = purchase.lines.new() + >>> line.product = product + >>> line.quantity = 10 + >>> purchase.click('quote') + >>> purchase.click('confirm') + >>> purchase.state + 'done' + >>> purchase.invoice_state + 'none' + >>> len(purchase.invoices) + 0 + +Manually create an invoice:: + + >>> purchase.click('manual_invoice') + >>> purchase.state + 'processing' + >>> purchase.invoice_state + 'pending' + +Change quantity on invoice and create a new invoice:: + + >>> invoice, = purchase.invoices + >>> line, = invoice.lines + >>> line.quantity = 5 + >>> invoice.save() + + >>> len(purchase.invoices) + 1 + >>> purchase.click('manual_invoice') + >>> len(purchase.invoices) + 2 diff --git a/modules/purchase/view/purchase_form.xml b/modules/purchase/view/purchase_form.xml index caaed55842..366b1df81c 100644 --- a/modules/purchase/view/purchase_form.xml +++ b/modules/purchase/view/purchase_form.xml @@ -77,6 +77,7 @@ this repository contains the full copyright notices and license terms. -->