odoo-addon-account-reconcile-oca 17.0.1.5.3__py3-none-any.whl → 17.0.1.5.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ Account Reconcile Oca
7
7
  !! This file is generated by oca-gen-addon-readme !!
8
8
  !! changes will be overwritten. !!
9
9
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10
- !! source digest: sha256:25a85af43d834abf67306c2d7fa108d6ecc44f76300ab92e54cc982f8cfe9334
10
+ !! source digest: sha256:7b48f8462e4b43596a339d2203f3f4566d5576b9f949ab75dca7f35d781d08c1
11
11
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12
12
 
13
13
  .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
@@ -5,7 +5,7 @@
5
5
  "name": "Account Reconcile Oca",
6
6
  "summary": """
7
7
  Reconcile addons for Odoo CE accounting""",
8
- "version": "17.0.1.5.3",
8
+ "version": "17.0.1.5.4",
9
9
  "license": "AGPL-3",
10
10
  "author": "CreuBlanca,Dixmit,Odoo Community Association (OCA)",
11
11
  "maintainers": ["etobella"],
@@ -163,7 +163,11 @@ class AccountAccountReconcile(models.Model):
163
163
  amount = 0.0
164
164
  for line_id in counterparts:
165
165
  lines = self._get_reconcile_line(
166
- self.env["account.move.line"].browse(line_id), "other", True, amount
166
+ self.env["account.move.line"].browse(line_id),
167
+ "other",
168
+ is_counterpart=True,
169
+ max_amount=amount,
170
+ move=True,
167
171
  )
168
172
  new_data["data"] += lines
169
173
  amount += sum(line["amount"] for line in lines)
@@ -8,7 +8,8 @@ from dateutil.relativedelta import relativedelta
8
8
 
9
9
  from odoo import Command, _, api, fields, models, tools
10
10
  from odoo.exceptions import UserError
11
- from odoo.tools import float_is_zero
11
+ from odoo.fields import first
12
+ from odoo.tools import float_compare, float_is_zero
12
13
 
13
14
 
14
15
  class AccountBankStatementLine(models.Model):
@@ -178,6 +179,18 @@ class AccountBankStatementLine(models.Model):
178
179
  )._default_reconcile_data()
179
180
  self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False)
180
181
 
182
+ def _get_amount_currency(self, line, dest_curr):
183
+ if line["line_currency_id"] == dest_curr.id:
184
+ amount = line["currency_amount"]
185
+ else:
186
+ amount = self.company_id.currency_id._convert(
187
+ line["amount"],
188
+ dest_curr,
189
+ self.company_id,
190
+ self.date,
191
+ )
192
+ return amount
193
+
181
194
  @api.onchange("add_account_move_line_id")
182
195
  def _onchange_add_account_move_line_id(self):
183
196
  if self.add_account_move_line_id:
@@ -185,9 +198,10 @@ class AccountBankStatementLine(models.Model):
185
198
  new_data = []
186
199
  is_new_line = True
187
200
  pending_amount = 0.0
201
+ currency = self._get_reconcile_currency()
188
202
  for line in data:
189
203
  if line["kind"] != "suspense":
190
- pending_amount += line["amount"]
204
+ pending_amount += self._get_amount_currency(line, currency)
191
205
  if self.add_account_move_line_id.id in line.get(
192
206
  "counterpart_line_ids", []
193
207
  ):
@@ -196,7 +210,11 @@ class AccountBankStatementLine(models.Model):
196
210
  new_data.append(line)
197
211
  if is_new_line:
198
212
  reconcile_auxiliary_id, lines = self._get_reconcile_line(
199
- self.add_account_move_line_id, "other", True, pending_amount
213
+ self.add_account_move_line_id,
214
+ "other",
215
+ is_counterpart=True,
216
+ max_amount=currency.round(pending_amount),
217
+ move=True,
200
218
  )
201
219
  new_data += lines
202
220
  self.reconcile_data_info = self._recompute_suspense_line(
@@ -210,9 +228,11 @@ class AccountBankStatementLine(models.Model):
210
228
  def _recompute_suspense_line(self, data, reconcile_auxiliary_id, manual_reference):
211
229
  can_reconcile = True
212
230
  total_amount = 0
231
+ currency_amount = 0
213
232
  new_data = []
214
233
  suspense_line = False
215
234
  counterparts = []
235
+ suspense_currency = self.foreign_currency_id or self.currency_id
216
236
  for line in data:
217
237
  if line.get("counterpart_line_ids"):
218
238
  counterparts += line["counterpart_line_ids"]
@@ -224,10 +244,29 @@ class AccountBankStatementLine(models.Model):
224
244
  if line["kind"] != "suspense":
225
245
  new_data.append(line)
226
246
  total_amount += line["amount"]
247
+ if not line.get("is_exchange_counterpart"):
248
+ # case of statement line with foreign_currency
249
+ if (
250
+ line["kind"] == "liquidity"
251
+ and line["line_currency_id"] != suspense_currency.id
252
+ ):
253
+ currency_amount += self.amount_currency
254
+ elif (
255
+ line.get("currency_amount")
256
+ and line.get("line_currency_id") == suspense_currency.id
257
+ ):
258
+ currency_amount += line.get("currency_amount")
259
+ else:
260
+ currency_amount += self.company_id.currency_id._convert(
261
+ line["amount"],
262
+ suspense_currency,
263
+ self.company_id,
264
+ self.date,
265
+ )
227
266
  else:
228
267
  suspense_line = line
229
268
  if not float_is_zero(
230
- total_amount, precision_digits=self.currency_id.decimal_places
269
+ total_amount, precision_digits=self.company_id.currency_id.decimal_places
231
270
  ):
232
271
  can_reconcile = False
233
272
  if suspense_line:
@@ -236,6 +275,7 @@ class AccountBankStatementLine(models.Model):
236
275
  "amount": -total_amount,
237
276
  "credit": total_amount if total_amount > 0 else 0.0,
238
277
  "debit": -total_amount if total_amount < 0 else 0.0,
278
+ "currency_amount": -currency_amount,
239
279
  }
240
280
  )
241
281
  else:
@@ -261,8 +301,8 @@ class AccountBankStatementLine(models.Model):
261
301
  "debit": -total_amount if total_amount < 0 else 0.0,
262
302
  "kind": "suspense",
263
303
  "currency_id": self.company_id.currency_id.id,
264
- "line_currency_id": self.company_id.currency_id.id,
265
- "currency_amount": -total_amount,
304
+ "line_currency_id": suspense_currency.id,
305
+ "currency_amount": -currency_amount,
266
306
  }
267
307
  reconcile_auxiliary_id += 1
268
308
  new_data.append(suspense_line)
@@ -282,11 +322,37 @@ class AccountBankStatementLine(models.Model):
282
322
  )
283
323
  or self.manual_account_id.id != line["account_id"][0]
284
324
  or self.manual_name != line["name"]
285
- or (self.manual_partner_id and self.manual_partner_id.display_name or False)
325
+ or (
326
+ self.manual_partner_id
327
+ and [self.manual_partner_id.id, self.manual_partner_id.display_name]
328
+ or [False, False]
329
+ )
286
330
  != line.get("partner_id")
287
331
  or self.analytic_distribution != line.get("analytic_distribution", False)
288
332
  )
289
333
 
334
+ def _check_reconcile_data_changed(self):
335
+ self.ensure_one()
336
+ data = self.reconcile_data_info.get("data", [])
337
+ liquidity_lines, _suspense_lines, _other_lines = self._seek_for_lines()
338
+ move_amount_cur = sum(liquidity_lines.mapped("amount_currency"))
339
+ move_credit = sum(liquidity_lines.mapped("credit"))
340
+ move_debit = sum(liquidity_lines.mapped("debit"))
341
+ stmt_amount_curr = stmt_debit = stmt_credit = 0.0
342
+ for line_data in data:
343
+ if line_data["kind"] != "liquidity":
344
+ continue
345
+ stmt_amount_curr += line_data["currency_amount"]
346
+ stmt_debit += line_data["debit"]
347
+ stmt_credit += line_data["credit"]
348
+ prec = self.currency_id.rounding
349
+ return (
350
+ float_compare(move_amount_cur, move_amount_cur, precision_rounding=prec)
351
+ != 0
352
+ or float_compare(move_credit, stmt_credit, precision_rounding=prec) != 0
353
+ or float_compare(move_debit, stmt_debit, precision_rounding=prec) != 0
354
+ )
355
+
290
356
  def _get_manual_delete_vals(self):
291
357
  return {
292
358
  "manual_reference": False,
@@ -362,7 +428,11 @@ class AccountBankStatementLine(models.Model):
362
428
 
363
429
  @api.onchange("manual_amount_in_currency")
364
430
  def _onchange_manual_amount_in_currency(self):
365
- if self.manual_line_id.exists() and self.manual_line_id:
431
+ if (
432
+ self.manual_line_id.exists()
433
+ and self.manual_line_id
434
+ and self.manual_kind != "liquidity"
435
+ ):
366
436
  self.manual_amount = self.manual_in_currency_id._convert(
367
437
  self.manual_amount_in_currency,
368
438
  self.company_id.currency_id,
@@ -372,7 +442,7 @@ class AccountBankStatementLine(models.Model):
372
442
  self._onchange_manual_reconcile_vals()
373
443
 
374
444
  def _get_manual_reconcile_vals(self):
375
- return {
445
+ vals = {
376
446
  "name": self.manual_name,
377
447
  "partner_id": (
378
448
  self.manual_partner_id
@@ -390,6 +460,19 @@ class AccountBankStatementLine(models.Model):
390
460
  "debit": self.manual_amount if self.manual_amount > 0 else 0.0,
391
461
  "analytic_distribution": self.analytic_distribution,
392
462
  }
463
+ liquidity_lines, _suspense_lines, _other_lines = self._seek_for_lines()
464
+ if self.manual_line_id and self.manual_line_id.id not in liquidity_lines.ids:
465
+ vals.update(
466
+ {
467
+ "currency_amount": self.manual_currency_id._convert(
468
+ self.manual_amount,
469
+ self.manual_in_currency_id,
470
+ self.company_id,
471
+ self.manual_line_id.date,
472
+ ),
473
+ }
474
+ )
475
+ return vals
393
476
 
394
477
  @api.onchange(
395
478
  "manual_account_id",
@@ -472,6 +555,8 @@ class AccountBankStatementLine(models.Model):
472
555
  def _reconcile_data_by_model(self, data, reconcile_model, reconcile_auxiliary_id):
473
556
  new_data = []
474
557
  liquidity_amount = 0.0
558
+ currency = self._get_reconcile_currency()
559
+ currency_amount = False
475
560
  for line_data in data:
476
561
  if line_data["kind"] == "suspense":
477
562
  continue
@@ -484,8 +569,18 @@ class AccountBankStatementLine(models.Model):
484
569
  new_line = line.copy()
485
570
  amount = line.get("balance")
486
571
  if self.foreign_currency_id:
487
- amount = self.foreign_currency_id.compute(
488
- amount, self.journal_id.currency_id or self.company_currency_id
572
+ amount = self.foreign_currency_id._convert(
573
+ amount,
574
+ self.journal_id.currency_id or self.company_currency_id,
575
+ self.company_id,
576
+ self.date,
577
+ )
578
+ if currency != self.company_id.currency_id:
579
+ currency_amount = self.company_id.currency_id._convert(
580
+ amount,
581
+ currency,
582
+ self.company_id,
583
+ self.date,
489
584
  )
490
585
  new_line.update(
491
586
  {
@@ -502,9 +597,9 @@ class AccountBankStatementLine(models.Model):
502
597
  .display_name,
503
598
  ],
504
599
  "date": fields.Date.to_string(self.date),
505
- "line_currency_id": self.company_id.currency_id.id,
600
+ "line_currency_id": currency.id,
506
601
  "currency_id": self.company_id.currency_id.id,
507
- "currency_amount": amount,
602
+ "currency_amount": currency_amount or amount,
508
603
  "name": line.get("name") or self.payment_ref,
509
604
  }
510
605
  )
@@ -525,7 +620,10 @@ class AccountBankStatementLine(models.Model):
525
620
  reconcile_auxiliary_id = 1
526
621
  for line in liquidity_lines:
527
622
  reconcile_auxiliary_id, lines = self._get_reconcile_line(
528
- line, "liquidity", reconcile_auxiliary_id=reconcile_auxiliary_id
623
+ line,
624
+ "liquidity",
625
+ reconcile_auxiliary_id=reconcile_auxiliary_id,
626
+ move=True,
529
627
  )
530
628
  data += lines
531
629
  if not from_unreconcile:
@@ -551,6 +649,7 @@ class AccountBankStatementLine(models.Model):
551
649
  self.manual_reference,
552
650
  )
553
651
  elif res and res.get("amls"):
652
+ # TODO should be signed in currency get_reconcile_currency
554
653
  amount = self.amount_total_signed
555
654
  for line in res.get("amls", []):
556
655
  reconcile_auxiliary_id, line_data = self._get_reconcile_line(
@@ -570,16 +669,89 @@ class AccountBankStatementLine(models.Model):
570
669
  self.manual_reference,
571
670
  )
572
671
  for line in other_lines:
573
- reconcile_auxiliary_id, lines = self._get_reconcile_line(
574
- line, "other", from_unreconcile=from_unreconcile
575
- )
576
- data += lines
672
+ partial_lines = self._all_partials_lines(line) if from_unreconcile else []
673
+ if partial_lines:
674
+ for reconciled_line in (
675
+ partial_lines.debit_move_id + partial_lines.credit_move_id - line
676
+ ):
677
+ if (
678
+ reconciled_line.move_id.journal_id
679
+ == self.company_id.currency_exchange_journal_id
680
+ ):
681
+ reconcile_auxiliary_id, lines = self._get_reconcile_line(
682
+ reconciled_line.move_id.line_ids - reconciled_line,
683
+ "other",
684
+ from_unreconcile=False,
685
+ move=True,
686
+ )
687
+ data += lines
688
+ continue
689
+ partial = partial_lines.filtered(
690
+ lambda r, line=reconciled_line: r.debit_move_id == line
691
+ or r.credit_move_id == line
692
+ )
693
+ partial_amount = sum(
694
+ partial.filtered(
695
+ lambda r, line=reconciled_line: r.credit_move_id == line
696
+ ).mapped("amount")
697
+ ) - sum(
698
+ partial.filtered(
699
+ lambda r, line=reconciled_line: r.debit_move_id == line
700
+ ).mapped("amount")
701
+ )
702
+ reconcile_auxiliary_id, lines = self._get_reconcile_line(
703
+ reconciled_line,
704
+ "other",
705
+ from_unreconcile={
706
+ "amount": partial_amount,
707
+ "credit": partial_amount > 0 and partial_amount,
708
+ "debit": partial_amount < 0 and -partial_amount,
709
+ "currency_amount": sum(
710
+ partial.filtered(
711
+ lambda r, line=reconciled_line: r.credit_move_id
712
+ == line
713
+ ).mapped("credit_amount_currency")
714
+ )
715
+ - sum(
716
+ partial.filtered(
717
+ lambda r, line=reconciled_line: r.debit_move_id
718
+ == line
719
+ ).mapped("debit_amount_currency")
720
+ ),
721
+ },
722
+ move=True,
723
+ )
724
+ data += lines
725
+ else:
726
+ reconcile_auxiliary_id, lines = self._get_reconcile_line(
727
+ line, "other", from_unreconcile=False
728
+ )
729
+ data += lines
730
+
577
731
  return self._recompute_suspense_line(
578
732
  data,
579
733
  reconcile_auxiliary_id,
580
734
  self.manual_reference,
581
735
  )
582
736
 
737
+ def _all_partials_lines(self, lines):
738
+ reconciliation_lines = lines.filtered(
739
+ lambda x: x.account_id.reconcile
740
+ or x.account_id.account_type in ("asset_cash", "liability_credit_card")
741
+ )
742
+ current_lines = reconciliation_lines
743
+ current_partials = self.env["account.partial.reconcile"]
744
+ partials = self.env["account.partial.reconcile"]
745
+ while current_lines:
746
+ current_partials = (
747
+ current_lines.matched_debit_ids + current_lines.matched_credit_ids
748
+ ) - current_partials
749
+ current_lines = (
750
+ current_partials.debit_move_id + current_partials.credit_move_id
751
+ ) - current_lines
752
+ partials += current_partials
753
+ return partials
754
+
583
755
  def clean_reconcile(self):
584
756
  self.reconcile_data_info = self._default_reconcile_data()
585
757
  self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False)
@@ -734,12 +906,13 @@ class AccountBankStatementLine(models.Model):
734
906
  to_reverse._reverse_moves(default_values_list, cancel=True)
735
907
 
736
908
  def _reconcile_move_line_vals(self, line, move_id=False):
737
- return {
909
+ vals = {
738
910
  "move_id": move_id or self.move_id.id,
739
911
  "account_id": line["account_id"][0],
740
912
  "partner_id": line.get("partner_id") and line["partner_id"][0],
741
913
  "credit": line["credit"],
742
914
  "debit": line["debit"],
915
+ "currency_id": line.get("line_currency_id", self.company_id.currency_id.id),
743
916
  "tax_ids": line.get("tax_ids", []),
744
917
  "tax_tag_ids": line.get("tax_tag_ids", []),
745
918
  "group_tax_id": line.get("group_tax_id"),
@@ -748,6 +921,11 @@ class AccountBankStatementLine(models.Model):
748
921
  "name": line.get("name"),
749
922
  "reconcile_model_id": line.get("reconcile_model_id"),
750
923
  }
924
+ if line.get("line_currency_id") and line["currency_id"] != line.get(
925
+ "line_currency_id"
926
+ ):
927
+ vals["amount_currency"] = line["currency_amount"]
928
+ return vals
751
929
 
752
930
  @api.model_create_multi
753
931
  def create(self, mvals):
@@ -771,7 +949,9 @@ class AccountBankStatementLine(models.Model):
771
949
  data = []
772
950
  for line in liquidity_lines:
773
951
  reconcile_auxiliary_id, lines = record._get_reconcile_line(
774
- line, "liquidity"
952
+ line,
953
+ "liquidity",
954
+ move=True,
775
955
  )
776
956
  data += lines
777
957
  reconcile_auxiliary_id = 1
@@ -783,10 +963,10 @@ class AccountBankStatementLine(models.Model):
783
963
  self.manual_reference,
784
964
  )
785
965
  elif res.get("amls"):
786
- amount = self.amount
966
+ amount = self.amount_currency or self.amount
787
967
  for line in res.get("amls", []):
788
968
  reconcile_auxiliary_id, line_datas = record._get_reconcile_line(
789
- line, "other", is_counterpart=True, max_amount=amount
969
+ line, "other", is_counterpart=True, max_amount=amount, move=True
790
970
  )
791
971
  amount -= sum(line_data.get("amount") for line_data in line_datas)
792
972
  data += line_datas
@@ -802,6 +982,67 @@ class AccountBankStatementLine(models.Model):
802
982
  )(self._prepare_reconcile_line_data(data["data"]))
803
983
  return result
804
984
 
985
+ def _synchronize_to_moves(self, changed_fields):
986
+ """We want to avoid to change stuff (mainly amounts ) in accounting entries
987
+ when some changes happen in the reconciliation widget. The only change
988
+ (among the fields triggering the synchronization) possible from the
989
+ reconciliation widget is the partner_id field.
990
+
991
+ So, in case of change on partner_id field we do not call super but make
992
+ only the required change (relative to partner) on accounting entries.
993
+
994
+ And if something else changes, we then re-define reconcile_data_info to
995
+ make the data consistent (for example, if debit/credit has changed by
996
+ applying a different rate or even if there was a correction on statement
997
+ line amount).
998
+ """
999
+ if self._context.get("skip_account_move_synchronization"):
1000
+ return
1001
+ if "partner_id" in changed_fields and not any(
1002
+ field_name in changed_fields
1003
+ for field_name in (
1004
+ "payment_ref",
1005
+ "amount",
1006
+ "amount_currency",
1007
+ "foreign_currency_id",
1008
+ "currency_id",
1009
+ )
1010
+ ):
1011
+ for st_line in self.with_context(skip_account_move_synchronization=True):
1012
+ (
1013
+ liquidity_lines,
1014
+ suspense_lines,
1015
+ _other_lines,
1016
+ ) = st_line._seek_for_lines()
1017
+ line_vals = {"partner_id": st_line.partner_id}
1018
+ line_ids_commands = [(1, liquidity_lines.id, line_vals)]
1019
+ if suspense_lines:
1020
+ line_ids_commands.append((1, suspense_lines.id, line_vals))
1021
+ st_line_vals = {"line_ids": line_ids_commands}
1022
+ if st_line.move_id.partner_id != st_line.partner_id:
1023
+ st_line_vals["partner_id"] = st_line.partner_id.id
1024
+ st_line.move_id.write(st_line_vals)
1025
+ else:
1026
+ super()._synchronize_to_moves(changed_fields=changed_fields)
1027
+
1028
+ if not any(
1029
+ field_name in changed_fields
1030
+ for field_name in (
1031
+ "payment_ref",
1032
+ "amount",
1033
+ "amount_currency",
1034
+ "foreign_currency_id",
1035
+ "currency_id",
1036
+ "partner_id",
1037
+ )
1038
+ ):
1039
+ return
1040
+ # reset reconcile_data_info if amounts are not consistent anymore with the
1041
+ # amounts of the accounting entries
1042
+ for st_line in self:
1043
+ if st_line._check_reconcile_data_changed():
1044
+ st_line.reconcile_data_info = st_line._default_reconcile_data()
1045
+
805
1046
  def _prepare_reconcile_line_data(self, lines):
806
1047
  new_lines = []
807
1048
  reverse_lines = {}
@@ -848,6 +1089,7 @@ class AccountBankStatementLine(models.Model):
848
1089
  is_counterpart=True,
849
1090
  reconcile_auxiliary_id=reconcile_auxiliary_id,
850
1091
  max_amount=original_amount,
1092
+ move=True,
851
1093
  )
852
1094
  new_data += lines
853
1095
  new_data.append(
@@ -895,6 +1137,7 @@ class AccountBankStatementLine(models.Model):
895
1137
  max_amount=False,
896
1138
  from_unreconcile=False,
897
1139
  reconcile_auxiliary_id=False,
1140
+ move=False,
898
1141
  ):
899
1142
  new_vals = super()._get_reconcile_line(
900
1143
  line,
@@ -902,29 +1145,48 @@ class AccountBankStatementLine(models.Model):
902
1145
  is_counterpart=is_counterpart,
903
1146
  max_amount=max_amount,
904
1147
  from_unreconcile=from_unreconcile,
1148
+ move=move,
905
1149
  )
906
1150
  rates = []
907
1151
  for vals in new_vals:
1152
+ rate = False
908
1153
  if vals["partner_id"] is False:
909
1154
  vals["partner_id"] = (False, self.partner_name)
910
- reconcile_auxiliary_id, rate = self._compute_exchange_rate(
911
- vals, line, reconcile_auxiliary_id
912
- )
1155
+ if vals.get("kind") not in ("suspense", "liquidity"):
1156
+ reconcile_auxiliary_id, rate = self._compute_exchange_rate(
1157
+ vals, line, reconcile_auxiliary_id
1158
+ )
913
1159
  if rate:
914
1160
  rates.append(rate)
915
1161
  new_vals += rates
916
1162
  return reconcile_auxiliary_id, new_vals
917
1163
 
918
1164
  def _get_exchange_rate_amount(self, amount, currency_amount, currency, line):
919
- return (
920
- currency._convert(
1165
+ if self.foreign_currency_id == currency:
1166
+ # take real rate of statement line to compute the exchange rate gain/loss
1167
+ real_rate = self.amount / self.amount_currency
1168
+ to_amount_journal_currency = currency_amount * real_rate
1169
+ to_amount_company_currency = self.currency_id._convert(
1170
+ to_amount_journal_currency,
1171
+ self.company_id.currency_id,
1172
+ self.company_id,
1173
+ self.date,
1174
+ )
1175
+ to_amount = self.company_id.currency_id.round(to_amount_company_currency)
1176
+ elif self.currency_id == currency and not self.foreign_currency_id:
1177
+ liquidity_lines, _suspense_lines, _other_lines = self._seek_for_lines()
1178
+ real_rate = (
1179
+ first(liquidity_lines).balance / first(liquidity_lines).amount_currency
1180
+ )
1181
+ to_amount = self.company_id.currency_id.round(currency_amount * real_rate)
1182
+ else:
1183
+ to_amount = currency._convert(
921
1184
  currency_amount,
922
1185
  self.company_id.currency_id,
923
1186
  self.company_id,
924
1187
  self.date,
925
1188
  )
926
- - amount
927
- )
1189
+ return self.company_id.currency_id.round(to_amount - amount)
928
1190
 
929
1191
  def _compute_exchange_rate(
930
1192
  self,
@@ -963,8 +1225,8 @@ class AccountBankStatementLine(models.Model):
963
1225
  "debit": amount if amount > 0 else 0.0,
964
1226
  "kind": "other",
965
1227
  "currency_id": self.company_id.currency_id.id,
966
- "line_currency_id": self.company_id.currency_id.id,
967
- "currency_amount": amount,
1228
+ "line_currency_id": currency.id,
1229
+ "currency_amount": 0,
968
1230
  }
969
1231
  reconcile_auxiliary_id += 1
970
1232
  return reconcile_auxiliary_id, data
@@ -991,3 +1253,10 @@ class AccountBankStatementLine(models.Model):
991
1253
  "split_line_id": self.id,
992
1254
  }
993
1255
  return action
1256
+
1257
+ def _get_reconcile_currency(self):
1258
+ return (
1259
+ self.foreign_currency_id
1260
+ or self.journal_id.currency_id
1261
+ or self.company_id.currency_id
1262
+ )
@@ -33,24 +33,46 @@ class AccountReconcileAbstract(models.AbstractModel):
33
33
  related="company_id.currency_id", string="Company Currency"
34
34
  )
35
35
 
36
+ def _get_reconcile_currency(self):
37
+ return self.currency_id or self.company_id._currency_id
38
+
36
39
  def _get_reconcile_line(
37
- self, line, kind, is_counterpart=False, max_amount=False, from_unreconcile=False
40
+ self,
41
+ line,
42
+ kind,
43
+ is_counterpart=False,
44
+ max_amount=False,
45
+ from_unreconcile=False,
46
+ move=False,
38
47
  ):
39
48
  date = self.date if "date" in self._fields else line.date
40
49
  original_amount = amount = net_amount = line.debit - line.credit
50
+ line_currency = line.currency_id
41
51
  if is_counterpart:
42
52
  currency_amount = -line.amount_residual_currency or line.amount_residual
43
53
  amount = -line.amount_residual
44
- currency = line.currency_id or self.company_id.currency_id
54
+ currency = line.currency_id or line.company_id.currency_id
45
55
  original_amount = net_amount = -line.amount_residual
46
56
  if max_amount:
47
- currency_max_amount = self.company_id.currency_id._convert(
48
- max_amount, currency, self.company_id, date
49
- )
57
+ dest_currency = self._get_reconcile_currency()
58
+ if currency == dest_currency:
59
+ real_currency_amount = currency_amount
60
+ elif self.company_id.currency_id == dest_currency:
61
+ real_currency_amount = amount
62
+ else:
63
+ real_currency_amount = self.company_id.currency_id._convert(
64
+ amount,
65
+ dest_currency,
66
+ self.company_id,
67
+ date,
68
+ )
50
69
  if (
51
- -currency_amount > currency_max_amount > 0
52
- or -currency_amount < currency_max_amount < 0
70
+ -real_currency_amount > max_amount > 0
71
+ or -real_currency_amount < max_amount < 0
53
72
  ):
73
+ currency_max_amount = self._get_reconcile_currency()._convert(
74
+ max_amount, currency, self.company_id, date
75
+ )
54
76
  amount = currency_max_amount
55
77
  net_amount = -max_amount
56
78
  currency_amount = -amount
@@ -61,8 +83,11 @@ class AccountReconcileAbstract(models.AbstractModel):
61
83
  date,
62
84
  )
63
85
  else:
64
- currency_amount = line.amount_currency
86
+ currency_amount = self.amount_currency or self.amount
87
+ line_currency = self._get_reconcile_currency()
65
88
  vals = {
89
+ "move_id": move and line.move_id.id,
90
+ "move": move and line.move_id.name,
66
91
  "reference": "account.move.line;%s" % line.id,
67
92
  "id": line.id,
68
93
  "account_id": [line.account_id.id, line.account_id.display_name],
@@ -76,7 +101,7 @@ class AccountReconcileAbstract(models.AbstractModel):
76
101
  "amount": amount,
77
102
  "net_amount": amount - net_amount,
78
103
  "currency_id": self.company_id.currency_id.id,
79
- "line_currency_id": line.currency_id.id,
104
+ "line_currency_id": line_currency.id,
80
105
  "currency_amount": currency_amount,
81
106
  "analytic_distribution": line.analytic_distribution,
82
107
  "kind": kind,
@@ -84,11 +109,11 @@ class AccountReconcileAbstract(models.AbstractModel):
84
109
  if from_unreconcile:
85
110
  vals.update(
86
111
  {
87
- "id": False,
88
- "counterpart_line_ids": (
89
- line.matched_debit_ids.mapped("debit_move_id")
90
- | line.matched_credit_ids.mapped("credit_move_id")
91
- ).ids,
112
+ "credit": vals["debit"] and from_unreconcile["debit"],
113
+ "debit": vals["credit"] and from_unreconcile["credit"],
114
+ "amount": from_unreconcile["amount"],
115
+ "net_amount": from_unreconcile["amount"],
116
+ "currency_amount": from_unreconcile["currency_amount"],
92
117
  }
93
118
  )
94
119
  if not float_is_zero(
@@ -367,7 +367,7 @@ ul.auto-toc {
367
367
  !! This file is generated by oca-gen-addon-readme !!
368
368
  !! changes will be overwritten. !!
369
369
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
370
- !! source digest: sha256:25a85af43d834abf67306c2d7fa108d6ecc44f76300ab92e54cc982f8cfe9334
370
+ !! source digest: sha256:7b48f8462e4b43596a339d2203f3f4566d5576b9f949ab75dca7f35d781d08c1
371
371
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
372
372
  <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/account-reconcile/tree/17.0/account_reconcile_oca"><img alt="OCA/account-reconcile" src="https://img.shields.io/badge/github-OCA%2Faccount--reconcile-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/account-reconcile-17-0/account-reconcile-17-0-account_reconcile_oca"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/account-reconcile&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
373
373
  <p>This addon allows to reconcile bank statements and account marked as
@@ -2,12 +2,15 @@
2
2
  import {formatDate, parseDate} from "@web/core/l10n/dates";
3
3
  import {formatMonetary} from "@web/views/fields/formatters";
4
4
  import {registry} from "@web/core/registry";
5
+ import {useService} from "@web/core/utils/hooks";
5
6
 
6
7
  const {Component} = owl;
7
8
 
8
9
  export class AccountReconcileDataWidget extends Component {
9
10
  setup() {
10
11
  super.setup(...arguments);
12
+ this.orm = useService("orm");
13
+ this.action = useService("action");
11
14
  this.foreignCurrency =
12
15
  this.props &&
13
16
  this.props.record &&
@@ -70,6 +73,15 @@ export class AccountReconcileDataWidget extends Component {
70
73
  });
71
74
  this.env.bus.trigger("RECONCILE_PAGE_NAVIGATE", triggerEv);
72
75
  }
76
+ async openMove(ev, moveId) {
77
+ ev.preventDefault();
78
+ ev.stopPropagation();
79
+ console.log(moveId);
80
+ const action = await this.orm.call("account.move", "get_formview_action", [
81
+ [moveId],
82
+ ]);
83
+ this.action.doAction(action);
84
+ }
73
85
  }
74
86
  AccountReconcileDataWidget.template = "account_reconcile_oca.ReconcileDataWidget";
75
87
 
@@ -114,7 +114,18 @@
114
114
  t-on-click="(ev) => this.selectReconcileLine(ev, reconcile_line)"
115
115
  t-att-class="'o_reconcile_widget_line ' + reconcile_line.kind + (props.record.data.manual_reference == reconcile_line.reference ? ' selected ' : ' ')"
116
116
  >
117
- <td t-esc="reconcile_line.account_id[1]" />
117
+ <td>
118
+ <div t-esc="reconcile_line.account_id[1]" />
119
+ <div t-if="reconcile_line.move_id">
120
+ <a
121
+ t-att-href="'/web#id=' + reconcile_line.move_id + '&amp;view_type=form&amp;model=account.move'"
122
+ class="o_form_uri"
123
+ t-on-click="(ev) => this.openMove(ev, reconcile_line.move_id)"
124
+ >
125
+ <small t-esc="reconcile_line.move" />
126
+ </a>
127
+ </div>
128
+ </td>
118
129
  <td>
119
130
  <span
120
131
  t-esc="reconcile_line.partner_id[1]"
@@ -91,7 +91,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
91
91
  inv1 = self.create_invoice(currency_id=self.currency_usd_id, invoice_amount=100)
92
92
  bank_stmt = self.acc_bank_stmt_model.create(
93
93
  {
94
- "company_id": self.env.ref("base.main_company").id,
95
94
  "journal_id": self.bank_journal_euro.id,
96
95
  "date": time.strftime("%Y-07-15"),
97
96
  "name": "test",
@@ -119,6 +118,42 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
119
118
  self.assertFalse(f.add_account_move_line_id)
120
119
  self.assertTrue(f.can_reconcile)
121
120
 
121
+ def test_manual_line_with_currency(self):
122
+ bank_stmt = self.acc_bank_stmt_model.create(
123
+ {
124
+ "journal_id": self.bank_journal_euro.id,
125
+ "date": time.strftime("%Y-07-15"),
126
+ "name": "test",
127
+ }
128
+ )
129
+ bank_stmt_line = self.acc_bank_stmt_line_model.create(
130
+ {
131
+ "name": "testLine",
132
+ "journal_id": self.bank_journal_euro.id,
133
+ "statement_id": bank_stmt.id,
134
+ "amount": 50,
135
+ "amount_currency": 100,
136
+ "foreign_currency_id": self.currency_usd_id,
137
+ "date": time.strftime("%Y-07-15"),
138
+ }
139
+ )
140
+ receivable_acc = self.company_data["default_account_receivable"]
141
+ with Form(
142
+ bank_stmt_line,
143
+ view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
144
+ ) as f:
145
+ self.assertFalse(f.can_reconcile)
146
+ f.manual_reference = "reconcile_auxiliary;1"
147
+ f.manual_account_id = receivable_acc
148
+ self.assertTrue(f.can_reconcile)
149
+ bank_stmt_line.reconcile_bank_line()
150
+ receivable_line = bank_stmt_line.line_ids.filtered(
151
+ lambda line: line.account_id == receivable_acc
152
+ )
153
+ self.assertEqual(receivable_line.currency_id.id, self.currency_usd_id)
154
+ self.assertEqual(receivable_line.amount_currency, -100)
155
+ self.assertEqual(receivable_line.balance, -50)
156
+
122
157
  def test_reconcile_invoice_reconcile_full(self):
123
158
  """
124
159
  We want to test the reconcile widget for bank statements on invoices.
@@ -130,7 +165,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
130
165
  )
131
166
  bank_stmt = self.acc_bank_stmt_model.create(
132
167
  {
133
- "company_id": self.env.ref("base.main_company").id,
134
168
  "journal_id": self.bank_journal_euro.id,
135
169
  "date": time.strftime("%Y-07-15"),
136
170
  "name": "test",
@@ -179,7 +213,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
179
213
  )
180
214
  bank_stmt = self.acc_bank_stmt_model.create(
181
215
  {
182
- "company_id": self.env.ref("base.main_company").id,
183
216
  "journal_id": self.bank_journal_euro.id,
184
217
  "date": time.strftime("%Y-07-15"),
185
218
  "name": "test",
@@ -242,7 +275,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
242
275
  )
243
276
  bank_stmt = self.acc_bank_stmt_model.create(
244
277
  {
245
- "company_id": self.env.ref("base.main_company").id,
246
278
  "journal_id": self.bank_journal_euro.id,
247
279
  "date": time.strftime("%Y-07-15"),
248
280
  "name": "test",
@@ -306,7 +338,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
306
338
  )
307
339
  bank_stmt = self.acc_bank_stmt_model.create(
308
340
  {
309
- "company_id": self.env.ref("base.main_company").id,
310
341
  "journal_id": self.bank_journal_euro.id,
311
342
  "date": time.strftime("%Y-07-15"),
312
343
  "name": "test",
@@ -360,7 +391,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
360
391
  """
361
392
  bank_stmt = self.acc_bank_stmt_model.create(
362
393
  {
363
- "company_id": self.env.ref("base.main_company").id,
364
394
  "journal_id": self.bank_journal_euro.id,
365
395
  "date": time.strftime("%Y-07-15"),
366
396
  "name": "test",
@@ -404,7 +434,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
404
434
  )
405
435
  bank_stmt = self.acc_bank_stmt_model.create(
406
436
  {
407
- "company_id": self.env.ref("base.main_company").id,
408
437
  "journal_id": self.bank_journal_euro.id,
409
438
  "date": time.strftime("%Y-07-15"),
410
439
  "name": "test",
@@ -459,7 +488,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
459
488
  )
460
489
  bank_stmt = self.acc_bank_stmt_model.create(
461
490
  {
462
- "company_id": self.env.ref("base.main_company").id,
463
491
  "journal_id": self.bank_journal_euro.id,
464
492
  "date": time.strftime("%Y-07-15"),
465
493
  "name": "test",
@@ -519,7 +547,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
519
547
 
520
548
  bank_stmt = self.acc_bank_stmt_model.create(
521
549
  {
522
- "company_id": self.env.ref("base.main_company").id,
523
550
  "journal_id": self.bank_journal_euro.id,
524
551
  "date": time.strftime("%Y-07-15"),
525
552
  "name": "test",
@@ -549,7 +576,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
549
576
  )
550
577
  bank_stmt = self.acc_bank_stmt_model.create(
551
578
  {
552
- "company_id": self.env.ref("base.main_company").id,
553
579
  "journal_id": self.bank_journal_euro.id,
554
580
  "date": time.strftime("%Y-07-15"),
555
581
  "name": "test",
@@ -594,6 +620,57 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
594
620
  self.assertTrue(reconcile_move.reversal_move_id)
595
621
  self.assertFalse(bank_stmt_line.is_reconciled)
596
622
 
623
+ def test_reconcile_model_with_foreign_currency(self):
624
+ """
625
+ We want to test what happens when we select a reconcile model to fill a
626
+ bank statement with a foreign currency.
627
+ """
628
+ bank_stmt = self.acc_bank_stmt_model.create(
629
+ {
630
+ "journal_id": self.bank_journal_usd.id,
631
+ "date": time.strftime("%Y-07-15"),
632
+ "name": "test",
633
+ }
634
+ )
635
+ bank_stmt_line = self.acc_bank_stmt_line_model.create(
636
+ {
637
+ "name": "testLine",
638
+ "journal_id": self.bank_journal_usd.id,
639
+ "statement_id": bank_stmt.id,
640
+ "amount": 100,
641
+ "date": time.strftime("%Y-07-15"),
642
+ }
643
+ )
644
+ with Form(
645
+ bank_stmt_line,
646
+ view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
647
+ ) as f:
648
+ self.assertFalse(f.can_reconcile)
649
+ f.manual_model_id = self.rule
650
+ self.assertTrue(f.can_reconcile)
651
+ number_of_lines = len(bank_stmt_line.reconcile_data_info["data"])
652
+ bank_stmt_line.reconcile_bank_line()
653
+ self.assertEqual(
654
+ number_of_lines, len(bank_stmt_line.reconcile_data_info["data"])
655
+ )
656
+ self.assertEqual(2, len(bank_stmt_line.move_id.line_ids))
657
+ self.assertTrue(
658
+ bank_stmt_line.move_id.line_ids.filtered(
659
+ lambda r: r.account_id == self.current_assets_account
660
+ )
661
+ )
662
+ expected_amount = bank_stmt_line._get_reconcile_currency()._convert(
663
+ bank_stmt_line.amount,
664
+ bank_stmt_line.company_id.currency_id,
665
+ bank_stmt_line.company_id,
666
+ bank_stmt_line.date,
667
+ )
668
+ self.assertEqual(
669
+ bank_stmt_line.move_id.line_ids[0].amount_currency, bank_stmt_line.amount
670
+ )
671
+ self.assertEqual(bank_stmt_line.move_id.line_ids[0].debit, expected_amount)
672
+ self.assertEqual(bank_stmt_line.move_id.line_ids[1].credit, expected_amount)
673
+
597
674
  # Testing to check functionality
598
675
 
599
676
  def test_reconcile_invoice_to_check_reconciled(self):
@@ -607,7 +684,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
607
684
  )
608
685
  bank_stmt = self.acc_bank_stmt_model.create(
609
686
  {
610
- "company_id": self.env.ref("base.main_company").id,
611
687
  "journal_id": self.bank_journal_euro.id,
612
688
  "date": time.strftime("%Y-07-15"),
613
689
  "name": "test",
@@ -649,7 +725,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
649
725
  """
650
726
  bank_stmt = self.acc_bank_stmt_model.create(
651
727
  {
652
- "company_id": self.env.ref("base.main_company").id,
653
728
  "journal_id": self.bank_journal_euro.id,
654
729
  "date": time.strftime("%Y-07-15"),
655
730
  "name": "test",
@@ -684,7 +759,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
684
759
  )
685
760
  bank_stmt = self.acc_bank_stmt_model.create(
686
761
  {
687
- "company_id": self.env.ref("base.main_company").id,
688
762
  "journal_id": self.bank_journal_euro.id,
689
763
  "date": time.strftime("%Y-07-15"),
690
764
  "name": "test",
@@ -722,7 +796,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
722
796
  )
723
797
  bank_stmt = self.acc_bank_stmt_model.create(
724
798
  {
725
- "company_id": self.env.ref("base.main_company").id,
726
799
  "journal_id": self.bank_journal_euro.id,
727
800
  "date": time.strftime("%Y-07-15"),
728
801
  "name": "test",
@@ -763,7 +836,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
763
836
  )
764
837
  bank_stmt = self.acc_bank_stmt_model.create(
765
838
  {
766
- "company_id": self.env.ref("base.main_company").id,
767
839
  "journal_id": self.bank_journal_euro.id,
768
840
  "date": time.strftime("%Y-07-15"),
769
841
  "name": "test",
@@ -804,7 +876,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
804
876
  )
805
877
  bank_stmt = self.acc_bank_stmt_model.create(
806
878
  {
807
- "company_id": self.env.ref("base.main_company").id,
808
879
  "journal_id": self.bank_journal_euro.id,
809
880
  "date": time.strftime("%Y-07-15"),
810
881
  "name": "test",
@@ -828,6 +899,7 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
828
899
  self.assertFalse(f.partner_id)
829
900
  f.manual_reference = "account.move.line;%s" % liquidity_lines.id
830
901
  f.manual_partner_id = inv1.partner_id
902
+ f.save()
831
903
  self.assertEqual(f.partner_id, inv1.partner_id)
832
904
  bank_stmt_line.clean_reconcile()
833
905
  # As we have set a partner, the cleaning should assign the invoice automatically
@@ -840,7 +912,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
840
912
  """
841
913
  bank_stmt = self.acc_bank_stmt_model.create(
842
914
  {
843
- "company_id": self.env.ref("base.main_company").id,
844
915
  "journal_id": self.bank_journal_euro.id,
845
916
  "date": time.strftime("%Y-07-15"),
846
917
  "name": "test",
@@ -891,7 +962,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
891
962
  """
892
963
  bank_stmt = self.acc_bank_stmt_model.create(
893
964
  {
894
- "company_id": self.env.ref("base.main_company").id,
895
965
  "journal_id": self.bank_journal_euro.id,
896
966
  "date": time.strftime("%Y-07-15"),
897
967
  "name": "test",
@@ -934,7 +1004,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
934
1004
 
935
1005
  bank_stmt = self.acc_bank_stmt_model.create(
936
1006
  {
937
- "company_id": self.env.ref("base.main_company").id,
938
1007
  "journal_id": self.bank_journal_euro.id,
939
1008
  "date": time.strftime("%Y-07-15"),
940
1009
  "name": "test",
@@ -998,7 +1067,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
998
1067
 
999
1068
  bank_stmt = self.acc_bank_stmt_model.create(
1000
1069
  {
1001
- "company_id": self.env.ref("base.main_company").id,
1002
1070
  "journal_id": self.bank_journal_euro.id,
1003
1071
  "date": time.strftime("%Y-07-15"),
1004
1072
  "name": "test",
@@ -1032,7 +1100,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
1032
1100
  inv1 = self.create_invoice(currency_id=self.currency_usd_id, invoice_amount=100)
1033
1101
  bank_stmt = self.acc_bank_stmt_model.create(
1034
1102
  {
1035
- "company_id": self.env.ref("base.main_company").id,
1036
1103
  "journal_id": self.bank_journal_usd.id,
1037
1104
  "date": time.strftime("%Y-07-15"),
1038
1105
  "name": "test",
@@ -1071,45 +1138,50 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
1071
1138
  )
1072
1139
 
1073
1140
  def test_journal_foreign_currency_change(self):
1141
+ cny = self.env.ref("base.CNY")
1142
+ cny.write({"active": True})
1143
+ cny_journal = self.env["account.journal"].create(
1144
+ {
1145
+ "name": "Bank CNY",
1146
+ "type": "bank",
1147
+ "currency_id": cny.id,
1148
+ }
1149
+ )
1074
1150
  self.env["res.currency.rate"].create(
1075
1151
  {
1076
- "currency_id": self.env.ref("base.EUR").id,
1077
- "name": time.strftime("%Y-07-14"),
1078
- "rate": 1.15,
1152
+ "name": time.strftime("%Y-09-10"),
1153
+ "currency_id": cny.id,
1154
+ "inverse_company_rate": 0.125989013758,
1155
+ }
1156
+ )
1157
+ self.env["res.currency.rate"].create(
1158
+ {
1159
+ "name": time.strftime("%Y-09-09"),
1160
+ "currency_id": cny.id,
1161
+ "inverse_company_rate": 0.126225969731,
1079
1162
  }
1080
1163
  )
1081
1164
  bank_stmt = self.acc_bank_stmt_model.create(
1082
1165
  {
1083
- "company_id": self.env.ref("base.main_company").id,
1084
- "journal_id": self.bank_journal_usd.id,
1085
- "date": time.strftime("%Y-07-15"),
1166
+ "journal_id": cny_journal.id,
1167
+ "date": time.strftime("%Y-09-10"),
1086
1168
  "name": "test",
1087
1169
  }
1088
1170
  )
1089
1171
  bank_stmt_line = self.acc_bank_stmt_line_model.create(
1090
1172
  {
1091
1173
  "name": "testLine",
1092
- "journal_id": self.bank_journal_usd.id,
1174
+ "journal_id": cny_journal.id,
1093
1175
  "statement_id": bank_stmt.id,
1094
- "amount": 100,
1095
- "date": time.strftime("%Y-07-15"),
1176
+ "amount": 259200,
1177
+ "date": time.strftime("%Y-09-10"),
1096
1178
  }
1097
1179
  )
1098
- with Form(
1099
- bank_stmt_line,
1100
- view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
1101
- ) as f:
1102
- line = f.reconcile_data_info["data"][0]
1103
- self.assertEqual(
1104
- line["currency_amount"],
1105
- 100,
1106
- )
1107
- self.env["res.currency.rate"].create(
1108
- {
1109
- "currency_id": self.env.ref("base.EUR").id,
1110
- "name": time.strftime("%Y-07-15"),
1111
- "rate": 1.2,
1112
- }
1180
+ inv1 = self._create_invoice(
1181
+ currency_id=cny.id,
1182
+ invoice_amount=259200,
1183
+ date_invoice=time.strftime("%Y-09-09"),
1184
+ auto_validate=True,
1113
1185
  )
1114
1186
  with Form(
1115
1187
  bank_stmt_line,
@@ -1118,8 +1190,17 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
1118
1190
  line = f.reconcile_data_info["data"][0]
1119
1191
  self.assertEqual(
1120
1192
  line["currency_amount"],
1121
- 100,
1193
+ 259200,
1122
1194
  )
1195
+ f.add_account_move_line_id = inv1.line_ids.filtered(
1196
+ lambda line: line.account_id.account_type == "asset_receivable"
1197
+ )
1198
+ self.assertTrue(f.can_reconcile)
1199
+ self.assertEqual(len(bank_stmt_line.reconcile_data_info["data"]), 3)
1200
+ exchange_line = bank_stmt_line.reconcile_data_info["data"][-1]
1201
+ self.assertEqual(exchange_line["amount"], 61.42)
1202
+ bank_stmt_line.reconcile_bank_line()
1203
+ self.assertEqual(inv1.payment_state, "paid")
1123
1204
 
1124
1205
  def test_invoice_foreign_currency_change(self):
1125
1206
  self.env["res.currency.rate"].create(
@@ -1144,7 +1225,6 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
1144
1225
  )
1145
1226
  bank_stmt = self.acc_bank_stmt_model.create(
1146
1227
  {
1147
- "company_id": self.env.ref("base.main_company").id,
1148
1228
  "journal_id": self.bank_journal_usd.id,
1149
1229
  "date": time.strftime("%Y-07-15"),
1150
1230
  "name": "test",
@@ -1222,3 +1302,90 @@ class TestReconciliationWidget(TestAccountReconciliationCommon):
1222
1302
  .account_type,
1223
1303
  "liability_payable",
1224
1304
  )
1305
+
1306
+ def test_invoice_foreign_currency_late_change_of_rate(self):
1307
+ # Test we can reconcile lines in foreign currency even if the rate was updated
1308
+ # late in odoo, meaning the statement line was created and the rate was updated
1309
+ # in odoo after that.
1310
+ self.env["res.currency.rate"].create(
1311
+ {
1312
+ "currency_id": self.env.ref("base.USD").id,
1313
+ "name": time.strftime("%Y-07-14"),
1314
+ "rate": 1.15,
1315
+ }
1316
+ )
1317
+ self.env["res.currency.rate"].create(
1318
+ {
1319
+ "currency_id": self.env.ref("base.USD").id,
1320
+ "name": time.strftime("%Y-07-15"),
1321
+ "rate": 1.2,
1322
+ }
1323
+ )
1324
+ inv1 = self._create_invoice(
1325
+ currency_id=self.currency_usd_id,
1326
+ invoice_amount=100,
1327
+ date_invoice=time.strftime("%Y-07-14"),
1328
+ auto_validate=True,
1329
+ )
1330
+ bank_stmt = self.acc_bank_stmt_model.create(
1331
+ {
1332
+ "journal_id": self.bank_journal_usd.id,
1333
+ "date": time.strftime("%Y-07-15"),
1334
+ "name": "test",
1335
+ }
1336
+ )
1337
+ bank_stmt_line = self.acc_bank_stmt_line_model.create(
1338
+ {
1339
+ "name": "testLine",
1340
+ "journal_id": self.bank_journal_usd.id,
1341
+ "statement_id": bank_stmt.id,
1342
+ "amount": 100,
1343
+ "date": time.strftime("%Y-07-16"),
1344
+ }
1345
+ )
1346
+ # rate of 07-16 is create after the statement line, meaning the rate of the
1347
+ # statement line is the one of the 07-15
1348
+ self.env["res.currency.rate"].create(
1349
+ {
1350
+ "currency_id": self.env.ref("base.USD").id,
1351
+ "name": time.strftime("%Y-07-16"),
1352
+ "rate": 1.25,
1353
+ }
1354
+ )
1355
+ liquidity_lines, suspense_lines, other_lines = bank_stmt_line._seek_for_lines()
1356
+ with Form(
1357
+ bank_stmt_line,
1358
+ view="account_reconcile_oca.bank_statement_line_form_reconcile_view",
1359
+ ) as f:
1360
+ line = f.reconcile_data_info["data"][0]
1361
+ self.assertEqual(
1362
+ line["currency_amount"],
1363
+ 100,
1364
+ )
1365
+ self.assertEqual(
1366
+ line["amount"],
1367
+ 83.33,
1368
+ )
1369
+ # check that adding a partner does not recompute the amounts on accounting
1370
+ # entries, but is still synchronized with accounting entries
1371
+ f.manual_reference = "account.move.line;%s" % liquidity_lines.id
1372
+ f.manual_partner_id = inv1.partner_id
1373
+ self.assertEqual(f.partner_id, inv1.partner_id)
1374
+ self.assertEqual(liquidity_lines.debit, 83.33)
1375
+ f.save()
1376
+ # check liquidity line did not recompute debit with the new rate with
1377
+ # partner change
1378
+ self.assertEqual(liquidity_lines.debit, 83.33)
1379
+ self.assertEqual(liquidity_lines.partner_id, inv1.partner_id)
1380
+ f.manual_reference = "account.move.line;%s" % line["id"]
1381
+ # simulate click on statement line, check amount does not recompute
1382
+ f.manual_partner_id = inv1.partner_id
1383
+ self.assertEqual(f.manual_amount, 83.33)
1384
+ # check currency amount is still fine
1385
+ self.assertEqual(f.reconcile_data_info["data"][0]["currency_amount"], 100)
1386
+ f.add_account_move_line_id = inv1.line_ids.filtered(
1387
+ lambda line: line.account_id.account_type == "asset_receivable"
1388
+ )
1389
+ self.assertEqual(3, len(f.reconcile_data_info["data"]))
1390
+ self.assertTrue(f.can_reconcile)
1391
+ self.assertEqual(f.reconcile_data_info["data"][-1]["amount"], 3.63)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: odoo-addon-account_reconcile_oca
3
- Version: 17.0.1.5.3
3
+ Version: 17.0.1.5.4
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: odoo-addon-account_reconcile_model_oca>=17.0dev,<17.1dev
6
6
  Requires-Dist: odoo-addon-account_statement_base>=17.0dev,<17.1dev
@@ -24,7 +24,7 @@ Account Reconcile Oca
24
24
  !! This file is generated by oca-gen-addon-readme !!
25
25
  !! changes will be overwritten. !!
26
26
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
27
- !! source digest: sha256:25a85af43d834abf67306c2d7fa108d6ecc44f76300ab92e54cc982f8cfe9334
27
+ !! source digest: sha256:7b48f8462e4b43596a339d2203f3f4566d5576b9f949ab75dca7f35d781d08c1
28
28
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
29
29
 
30
30
  .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
@@ -1,6 +1,6 @@
1
- odoo/addons/account_reconcile_oca/README.rst,sha256=OmB1sidJ_RcCKQiCwdUpFYd5v7sBalxb8a1kKGSCIlU,3703
1
+ odoo/addons/account_reconcile_oca/README.rst,sha256=9cy5LDNzoKV5x490CP1NpNXtrF5WhMnIdaJqMh_ASfo,3703
2
2
  odoo/addons/account_reconcile_oca/__init__.py,sha256=vqRYeBgCVZMpZhYvILSxVsNLC9V7zDnvxMnKU8RQP94,55
3
- odoo/addons/account_reconcile_oca/__manifest__.py,sha256=qKdy4crOTlbRjwXq71ptSEu7mpbxwEvEKz-QsVgGCzs,1865
3
+ odoo/addons/account_reconcile_oca/__manifest__.py,sha256=P_Rw_ev5GFEAaW_MBlIAdxHwW7z1wvpqKdUDmJJDZQU,1865
4
4
  odoo/addons/account_reconcile_oca/hooks.py,sha256=SfJ-GlIGYL1kf8xhQs5qDqwNnE8S9Gs-5dP3vOD-IMM,182
5
5
  odoo/addons/account_reconcile_oca/demo/demo.xml,sha256=6k0uK-H1aBiyogVNhQMQfFGL5zUfUGV2M-sSV6LHeUs,204
6
6
  odoo/addons/account_reconcile_oca/i18n/account_reconcile_oca.pot,sha256=RnxHPjPpQk_qZs4ZHFvH13KDHnqQO-ppgcUnULeZxwo,25700
@@ -17,12 +17,12 @@ odoo/addons/account_reconcile_oca/i18n/tr.po,sha256=jdrItgYAt4JAEpwbuSuy2vAq-Si_
17
17
  odoo/addons/account_reconcile_oca/i18n/zh.po,sha256=i7HjN44XrHzZ-qIG994NXgaEvjYU10Cw0Jcp7HQGAWk,25765
18
18
  odoo/addons/account_reconcile_oca/i18n/zh_CN.po,sha256=gHrxzsXR_B6rbs_0iU8_-9doeTZwiy-HmPtFoiiZJ3s,27504
19
19
  odoo/addons/account_reconcile_oca/models/__init__.py,sha256=28wbZjUZa30uHQY10BMJtKQ_BqJgwLQMQvB9uv0H_fY,282
20
- odoo/addons/account_reconcile_oca/models/account_account_reconcile.py,sha256=ndgMwziMe2GsqEucoH0vZMqVmubgBcajJahh1LcNK9s,6709
20
+ odoo/addons/account_reconcile_oca/models/account_account_reconcile.py,sha256=j5tTL1RvlDIYAjrVNf7kt1tf58-TzFL7iqocSfMjo9g,6811
21
21
  odoo/addons/account_reconcile_oca/models/account_bank_statement.py,sha256=CFqlLLtmTqmt5yH3v_pkUfkZezSb1nEhBTu2J5gCjxM,1034
22
- odoo/addons/account_reconcile_oca/models/account_bank_statement_line.py,sha256=0Hnz98yjtOA1Vf23lmZYFSpX1iWHan-9CIc7YdmvXEA,39877
22
+ odoo/addons/account_reconcile_oca/models/account_bank_statement_line.py,sha256=CjZvCNLgki-d5g-DHX3TtzmbgKd2TcQ0Ln9_GRf2PSM,51781
23
23
  odoo/addons/account_reconcile_oca/models/account_journal.py,sha256=PXXoGnByO7ve9_G09tnn95HKJoLvXM1pDxHDdd7bpU4,1359
24
24
  odoo/addons/account_reconcile_oca/models/account_move_line.py,sha256=PfdU6cfrKjKMqL_qGqNdyPUWg-frGQ6nR5uCoYtW38M,1212
25
- odoo/addons/account_reconcile_oca/models/account_reconcile_abstract.py,sha256=I23ZF31OvY5EqtfNHfm2Pir3JJ3z5CaYjEFr1OZ8oWI,3983
25
+ odoo/addons/account_reconcile_oca/models/account_reconcile_abstract.py,sha256=vtQX3yQqUslslTny7QSbbrh0u4vfsCG4p5MAKHAOlyA,5011
26
26
  odoo/addons/account_reconcile_oca/models/res_company.py,sha256=IaSLPwHJZre5RYPVW8V6mnSoxltS_w0GUN1Ev-cfcB4,354
27
27
  odoo/addons/account_reconcile_oca/models/res_config_settings.py,sha256=AuenxX0UfqYWWP-QvtB0irSf_JuWVh4a9QylPfl-Lxc,325
28
28
  odoo/addons/account_reconcile_oca/readme/CONTRIBUTORS.md,sha256=subC7gWq_kRC_nJfLRfrI_IXlNoGgWaq9Es5qXuMa1w,16
@@ -31,7 +31,7 @@ odoo/addons/account_reconcile_oca/readme/ROADMAP.md,sha256=fxR8QnC8BkHyODdPScpgJ
31
31
  odoo/addons/account_reconcile_oca/readme/USAGE.md,sha256=yNLGo35X7TW2TLyJqHU-gdQiXazW8Iu59rI0dpa4WjM,365
32
32
  odoo/addons/account_reconcile_oca/security/ir.model.access.csv,sha256=XfN2EKOoChlEDonVd5DtodVAQyRbShiJ8nrXx6EwNmM,339
33
33
  odoo/addons/account_reconcile_oca/static/description/icon.png,sha256=6xBPJauaFOF0KDHfHgQopSc28kKvxMaeoQFQWZtfZDo,9455
34
- odoo/addons/account_reconcile_oca/static/description/index.html,sha256=TLpztMdREtvemnGpBSHZUWykkj_xJHY58TY25Ja-HR0,13790
34
+ odoo/addons/account_reconcile_oca/static/description/index.html,sha256=dr22xFUNNb6Hu-T4XpmXmHq6NCPkzVK6EGPDU-zxL08,13790
35
35
  odoo/addons/account_reconcile_oca/static/src/js/reconcile/reconcile_controller.esm.js,sha256=k85Scd4skR5y56TvFVEqB_uf7vCD9Y7RpInETuNBAc8,4891
36
36
  odoo/addons/account_reconcile_oca/static/src/js/reconcile/reconcile_kanban_record.esm.js,sha256=ewNK1VQgFZWccTiyJMKYkOG6KtbHHVnI2pdNy9kjkig,467
37
37
  odoo/addons/account_reconcile_oca/static/src/js/reconcile/reconcile_renderer.esm.js,sha256=9NSr3iZ7H_QGTpixW5D4WEJuD_u4KRpdYwRmKqcML_k,2117
@@ -46,14 +46,14 @@ odoo/addons/account_reconcile_oca/static/src/js/reconcile_move_line/reconcile_mo
46
46
  odoo/addons/account_reconcile_oca/static/src/js/reconcile_move_line/reconcile_move_line_renderer.esm.js,sha256=TwiLpAbPJtr9VDZs8ZDIVFoieiogueHnpw0n0a1P_Kk,605
47
47
  odoo/addons/account_reconcile_oca/static/src/js/reconcile_move_line/reconcile_move_line_view.esm.js,sha256=TFHNQB-A2wyBlfUHXpqKBt_-ICY0pbO90n4ao9dCVBI,510
48
48
  odoo/addons/account_reconcile_oca/static/src/js/widgets/reconcile_chatter_field.esm.js,sha256=w8vDPCCovRJRKYZtp1XnlWGNGXDfvoJYuttKF-jpTHw,902
49
- odoo/addons/account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js,sha256=YrWCTvLIZqAE1McQMDVKqgIoJIZA1r1hAaxfd3l4Ulo,2920
49
+ odoo/addons/account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js,sha256=-m-It4krr0hLeRoM7VtiOdy7ZsutJPxokCY7HZRjfD8,3335
50
50
  odoo/addons/account_reconcile_oca/static/src/js/widgets/reconcile_move_line_widget.esm.js,sha256=G5lbsagkTYErqNlPvhW0vfljQWRKI4eI5spDlH3_emk,3772
51
51
  odoo/addons/account_reconcile_oca/static/src/js/widgets/selection_badge_uncheck.esm.js,sha256=yFqG5t4wKA47k-wUMzPuDZ1AvILft6_hucGOz2YHWGE,836
52
52
  odoo/addons/account_reconcile_oca/static/src/scss/reconcile.scss,sha256=b6clO2yWT91dEdTE3RcdORFE6dNqqzsXz3S0Rc5afZU,2318
53
- odoo/addons/account_reconcile_oca/static/src/xml/reconcile.xml,sha256=hnXWp6pYXbAGf9EjmZaGDb4GHe1CBLGZMbbwbz0WuaY,8637
53
+ odoo/addons/account_reconcile_oca/static/src/xml/reconcile.xml,sha256=zrLcG1bVWDwjm3PzrStw8MgX9IaTtANDBLVF3X51-ms,9266
54
54
  odoo/addons/account_reconcile_oca/tests/__init__.py,sha256=8JhP4auByShS8Z_Ik5dShMuWdh1kBlYP_DLI4Ku8XWA,79
55
55
  odoo/addons/account_reconcile_oca/tests/test_account_reconcile.py,sha256=MP5M-NsVhMoYiU675hr1nsvZRRK-4e5KUYMCCKTUHB8,10807
56
- odoo/addons/account_reconcile_oca/tests/test_bank_account_reconcile.py,sha256=jy8F3hWd85x_1tBgPlT84PvfTrqFXLQogx_Dwv-wXEo,47041
56
+ odoo/addons/account_reconcile_oca/tests/test_bank_account_reconcile.py,sha256=km7zs8uO6wabfryrsg8VLd5yT0AhkRaI0nI_MHOx-Es,53449
57
57
  odoo/addons/account_reconcile_oca/views/account_account.xml,sha256=0RiPmzfRUj54oMYKWM7mLnZL-5IhI5W8fiRruHL0SYc,866
58
58
  odoo/addons/account_reconcile_oca/views/account_account_reconcile.xml,sha256=2FKosNTl4hVC_yVq8uRzZvMMrXyEttK_YTsdnV8o0NI,7016
59
59
  odoo/addons/account_reconcile_oca/views/account_bank_statement.xml,sha256=QrjQZ3OTsnbopdLnnsFaZ0f7ncLkOUbtVj9osXdxw5k,2436
@@ -62,7 +62,7 @@ odoo/addons/account_reconcile_oca/views/account_journal.xml,sha256=T5tZ5Ev0psYp7
62
62
  odoo/addons/account_reconcile_oca/views/account_move.xml,sha256=BnvchVpOJ31M-kCoe6AHDxHMfrEEgJTPPysxguX3xgE,975
63
63
  odoo/addons/account_reconcile_oca/views/account_move_line.xml,sha256=Kr_ET1IAat3B8UoW6VPQO3q88JLVdE5-5jpb13ioCbY,5405
64
64
  odoo/addons/account_reconcile_oca/views/res_config_settings.xml,sha256=PxIqWILg_L_ahR-SDlDWEqWDJKNumPRifFAGF0BG57E,807
65
- odoo_addon_account_reconcile_oca-17.0.1.5.3.dist-info/METADATA,sha256=5e0AIOCQUtjdrfq5VsUxmdHuhDbgJgBgwO2ehMoFBII,4388
66
- odoo_addon_account_reconcile_oca-17.0.1.5.3.dist-info/WHEEL,sha256=9fEMia4zL7ZuZbnCOrcYogUhmn4XFIVaJ8G4YGI31xc,81
67
- odoo_addon_account_reconcile_oca-17.0.1.5.3.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
68
- odoo_addon_account_reconcile_oca-17.0.1.5.3.dist-info/RECORD,,
65
+ odoo_addon_account_reconcile_oca-17.0.1.5.4.dist-info/METADATA,sha256=484jst0lJuJFJm_rjELbKOiImdmhPSM3v1gpSqNf3m4,4388
66
+ odoo_addon_account_reconcile_oca-17.0.1.5.4.dist-info/WHEEL,sha256=9fEMia4zL7ZuZbnCOrcYogUhmn4XFIVaJ8G4YGI31xc,81
67
+ odoo_addon_account_reconcile_oca-17.0.1.5.4.dist-info/top_level.txt,sha256=QE6RBQ0QX5f4eFuUcGgU5Kbq1A_qJcDs-e_vpr6pmfU,4
68
+ odoo_addon_account_reconcile_oca-17.0.1.5.4.dist-info/RECORD,,