forked from OCA/account-invoicing
-
Notifications
You must be signed in to change notification settings - Fork 4
/
stock_invoice_onshipping.py
328 lines (296 loc) · 13.3 KB
/
stock_invoice_onshipping.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# Copyright (C) 2020-TODAY KMEE
# Copyright (C) 2021-TODAY Akretion
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
class StockInvoiceOnshipping(models.TransientModel):
_inherit = "stock.invoice.onshipping"
@api.model
def _default_has_down_payment(self):
pickings = self._load_pickings()
sale_pickings = pickings.filtered(lambda pk: pk.sale_id)
downpayment_lines = False
if sale_pickings:
for pick in sale_pickings:
# order = pick.sale_id
# sale_lines = order.mapped("order_line")
if pick.sale_id.order_line.filtered(lambda ln: ln.is_downpayment):
downpayment_lines = True
return downpayment_lines
deduct_down_payments = fields.Boolean("Deduct down payments", default=True)
has_down_payments = fields.Boolean(
"Has down payments", default=_default_has_down_payment, readonly=True
)
def _build_invoice_values_from_pickings(self, pickings):
invoice, values = super()._build_invoice_values_from_pickings(pickings)
sale_pickings = pickings.filtered(lambda pk: pk.sale_id)
# Refund case don't get values from Sale Dict
# TODO: Should get any value?
if sale_pickings and self._get_invoice_type() != "out_refund":
# Case more than one Sale Order the fields below will be join
# the others will be overwritting, as done in sale module,
# one more field include here Note/Narration
payment_refs = set()
refs = set()
# Include Narration
narration = set()
for pick in sale_pickings.sorted(key=lambda p: p.name):
# Other modules can included new fields in Sale Order and include
# this fields in the dict of creation Invoice from sale, for
# example:
# - account_payment_sale
# https://github.com/OCA/bank-payment/blob/14.0/
# account_payment_sale/models/sale_order.py#L41
# - sale_commssion
# https://github.com/OCA/commission/blob/14.0/
# sale_commission/models/sale_order.py#L64
# To avoid the necessity of a 'glue' module the method get the
# values from _prepare_invoice but removed some fields of the
# original method, given priority for values from
# stock_picking_invoicing dict, for now it's seems the best to
# way to avoid the 'glue' modules problem.
sale_values = pick.sale_id._prepare_invoice()
# Fields to Join
# origins.add(sale_values["invoice_origin"])
payment_refs.add(sale_values["payment_reference"])
refs.add(sale_values["ref"])
narration.add(sale_values["narration"])
# Original dict from sale module, for reference:
# Fields to get:
# "ref": self.client_order_ref or ''
# "narration": self.note,
# "campaign_id": self.campaign_id.id,
# "medium_id": self.medium_id.id,
# "source_id": self.source_id.id,
# "team_id": self.team_id.id,
# "partner_shipping_id": self.partner_shipping_id.id,
# "partner_bank_id": self.company_id.partner_id.bank_ids.
# filtered(lambda bank: bank.company_id.id in
# (self.company_id.id, False))[:1].id,
# "invoice_payment_term_id": self.payment_term_id.id,
# "payment_reference": self.reference,
# "transaction_ids": [(6, 0, self.transaction_ids.ids)],
# Fields to remove
vals_to_remove = {
"move_type",
"currency_id",
"user_id",
"invoice_user_id",
"partner_id",
"fiscal_position_id",
"journal_id", # company comes from the journal
"invoice_origin",
"invoice_line_ids",
"company_id",
# Another fields
"__last_update",
"display_name",
}
sale_values_rm = {
k: sale_values[k] for k in set(sale_values) - vals_to_remove
}
values.update(sale_values_rm)
# Fields to join
if len(sale_pickings) > 1:
values.update(
{
"ref": ", ".join(refs)[:2000],
# In this case Origin get Pickings Names
# "invoice_origin": ", ".join(origins),
"payment_reference": len(payment_refs) == 1
and payment_refs.pop()
or False,
"narration": ", ".join(narration),
}
)
return invoice, values
# Check the comment below
# def _get_picking_key(self, picking):
# key = super()._get_picking_key(picking)
# if picking.sale_id:
# key = key + (
# picking.sale_id.payment_term_id,
# picking.sale_id.fiscal_position_id,
# picking.sale_id.commitment_date,
# picking.sale_id.analytic_account_id,
# picking.sale_id.pricelist_id,
# picking.sale_id.company_id,
# )
# return key
def _get_move_key(self, move):
key = super()._get_move_key(move)
if move.sale_line_id:
# TODO: Analise if Sale Lines should be grouped.
# For now remains a problem https://github.com/odoo/odoo/pull/77195
# with field qty_invoiced at sale.order.line, when a invoice line
# has more than one sale line related the field show the total QTY
# of those lines, e.g:
# product_uom_qty | qty_invoiced
# 2.0 | 4.0
# key = key + (
# move.sale_line_id.price_unit,
# move.sale_line_id.customer_lead,
# move.sale_line_id.currency_id,
# move.sale_line_id.tax_id,
# move.sale_line_id.analytic_tag_ids,
# )
key = key + (move.sale_line_id,)
return key
def _get_invoice_line_values(self, moves, invoice_values, invoice):
values = super()._get_invoice_line_values(moves, invoice_values, invoice)
move = fields.first(moves)
if move.sale_line_id:
# Vals informed in any case
values["sale_line_ids"] = [(6, 0, moves.sale_line_id.ids)]
values[
"analytic_account_id"
] = moves.sale_line_id.order_id.analytic_account_id.id
values["analytic_tag_ids"] = [
(6, 0, moves.sale_line_id.analytic_tag_ids.ids)
]
# Refund case don't get values from Sale Line Dict
# TODO: Should get any value?
if self._get_invoice_type() != "out_refund":
# Same make above, get fields informed in Sale Line dict
sale_line_values = move.sale_line_id._prepare_invoice_line()
# Original fields from sale module
# Fields do get
# "sequence": self.sequence,
# "discount": self.discount,
# Fields to remove
vals_to_remove = {
"display_type",
"name",
"product_id",
"product_uom_id",
"quantity",
"price_unit",
"tax_ids",
"analytic_account_id",
"analytic_tag_ids",
"sale_line_ids",
# another fields
"__last_update",
"display_name",
}
sale_line_values_rm = {
k: sale_line_values[k]
for k in set(sale_line_values) - vals_to_remove
}
values.update(sale_line_values_rm)
return values
def _create_invoice(self, invoice_values):
"""Override this method if you need to change any values of the
invoice and the lines before the invoice creation
:param invoice_values: dict with the invoice and its lines
:return: invoice
"""
pickings = self._load_pickings()
sale_pickings = pickings.filtered(
lambda pk: pk.sale_id
# Check Sales Ungrouped
and pk.id in invoice_values.get("picking_ids")[0][2]
)
# Refund case don't included Section, Note or DownPayments
if not sale_pickings or self._get_invoice_type() == "out_refund":
return super()._create_invoice(invoice_values)
# Check Other Sale Lines
# Section, Note and Down Payments
section_note_lines = down_payment_lines = self.env["sale.order.line"]
# Resequencing
invoice_item_sequence = (
0 # Incremental sequencing to keep the lines order on the invoice.
)
invoice_item_seq_dict = {}
for pick in sale_pickings.sorted(key=lambda p: p.name):
order = pick.sale_id.with_company(pick.sale_id.company_id)
invoiceable_lines = order._get_invoiceable_lines(final=True)
section_note_lines |= invoiceable_lines.filtered(
lambda ln: ln.display_type in ("line_section", "line_note")
)
down_payment_lines |= invoiceable_lines.filtered(
lambda ln: ln.is_downpayment
)
# Use for Resequencing
for line in order.order_line:
invoice_item_seq_dict[line.id] = invoice_item_sequence
invoice_item_sequence += 1
# Sections and Notes
if section_note_lines:
section_note_vals = []
for line in section_note_lines:
sale_line_vals = line._prepare_invoice_line()
# Change [(4, 59)] for [(6, 0, [59])] to avoid error
# in method to Resequencing
sale_line_vals["sale_line_ids"] = [
(6, 0, [sale_line_vals.get("sale_line_ids")[0][1]])
]
section_note_vals.append((0, 0, sale_line_vals))
invoice_values["invoice_line_ids"] += section_note_vals
# Resequencing, necessary in the case of Grouping Sale Orders
for line in invoice_values.get("invoice_line_ids"):
# [(6, 0, {})]
if line[2]:
sale_line = line[2].get("sale_line_ids")
if sale_line:
# [(6, 0, [58])]
line[2]["sequence"] = invoice_item_seq_dict.get(sale_line[0][2][0])
# Down Payments
# After the Resequencing to put it in the end of Invoice
if down_payment_lines:
down_payment_vals = []
down_payment_section_added = False
for line in down_payment_lines:
if not down_payment_section_added and line.is_downpayment:
# Create a dedicated section for the down payments
# (put at the end of the invoiceable_lines)
down_payment_vals.append(
(
0,
0,
line.order_id._prepare_down_payment_section_line(
sequence=invoice_item_sequence,
),
),
)
down_payment_section_added = True
invoice_item_sequence += 1
if line.is_downpayment:
down_payment_vals.append(
(
0,
0,
line._prepare_invoice_line(
sequence=invoice_item_sequence,
),
),
)
invoice_item_sequence += 1
invoice_values["invoice_line_ids"] += down_payment_vals
moves = (
self.env["account.move"]
.sudo()
.with_context(default_move_type="out_invoice")
.create(invoice_values)
)
# param Final: if True, refunds will be generated if necessary
final = self.deduct_down_payments
if final:
moves.sudo().filtered(
lambda m: m.amount_total < 0
).action_switch_invoice_into_refund_credit_note()
for move in moves:
move.message_post_with_view(
"mail.message_origin_link",
# In this case the Origin are Pickings
# values={
# "self": move,
# "origin": move.line_ids.mapped("sale_line_ids.order_id"),
# },
values={
"self": move.picking_ids,
"origin": move.picking_ids,
},
subtype_id=self.env.ref("mail.mt_note").id,
)
return moves