You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

524 lines
19 KiB

  1. import base64
  2. from odoo import api, fields, models
  3. TYPE_MAP = {
  4. "subscription": "subscribed",
  5. "transfer": "transfered",
  6. "sell_back": "resold",
  7. }
  8. REPORT_DIC = {
  9. "subscription": (
  10. "easy_my_coop_taxshelter_report.action_tax_shelter_subscription_report",
  11. "Tax Shelter Subscription",
  12. ),
  13. "shares": (
  14. "easy_my_coop_taxshelter_report.action_tax_shelter_shares_report",
  15. "Tax Shelter Shares",
  16. ),
  17. }
  18. class TaxShelterDeclaration(models.Model):
  19. _name = "tax.shelter.declaration"
  20. _description = "Tax Shelter Declaration"
  21. name = fields.Char(string="Declaration year", required=True)
  22. fiscal_year = fields.Char(String="Fiscal year", required=True)
  23. tax_shelter_certificates = fields.One2many(
  24. "tax.shelter.certificate",
  25. "declaration_id",
  26. string="Tax shelter certificates",
  27. readonly=True,
  28. )
  29. date_from = fields.Date(string="Date from", required=True)
  30. date_to = fields.Date(string="Date to", required=True)
  31. month_from = fields.Char(String="Month from", required=True)
  32. month_to = fields.Char(String="Month to", required=True)
  33. tax_shelter_percentage = fields.Selection(
  34. [("30", "30%"), ("45", "45%")],
  35. string="Tax Shelter percentage",
  36. required=True,
  37. )
  38. state = fields.Selection(
  39. [
  40. ("draft", "Draft"),
  41. ("computed", "Computed"),
  42. ("validated", "Validated"),
  43. ],
  44. string="State",
  45. required=True,
  46. default="draft",
  47. )
  48. company_id = fields.Many2one(
  49. "res.company",
  50. string="Company",
  51. required=True,
  52. change_default=True,
  53. readonly=True,
  54. default=lambda self: self.env["res.company"]._company_default_get(),
  55. )
  56. tax_shelter_capital_limit = fields.Float(
  57. string="Tax shelter capital limit", required=True
  58. )
  59. previously_subscribed_capital = fields.Float(
  60. String="Capital previously subscribed", readonly=True
  61. )
  62. excluded_cooperator = fields.Many2many(
  63. "res.partner",
  64. string="Excluded cooperator",
  65. domain=[("cooperator", "=", True)],
  66. help="If these cooperator have"
  67. " subscribed share during the time"
  68. " frame of this Tax Shelter "
  69. "Declaration. They will be marked "
  70. "as non eligible",
  71. )
  72. def _excluded_from_declaration(self, entry):
  73. if entry.date >= self.date_from and entry.date <= self.date_to:
  74. declaration = self
  75. else:
  76. declaration = self.search(
  77. [
  78. ("date_from", "<=", entry.date),
  79. ("date_to", ">=", entry.date),
  80. ]
  81. )
  82. if entry.partner_id.id in declaration.excluded_cooperator.ids:
  83. return True
  84. return False
  85. def _prepare_line(self, certificate, entry, ongoing_capital_sub, excluded):
  86. line_vals = {}
  87. line_vals["tax_shelter_certificate"] = certificate.id
  88. line_vals["share_type"] = entry.share_product_id.id
  89. line_vals["share_short_name"] = entry.share_short_name
  90. line_vals["share_unit_price"] = entry.share_unit_price
  91. line_vals["quantity"] = entry.quantity
  92. line_vals["transaction_date"] = entry.date
  93. line_vals["type"] = TYPE_MAP[entry.type]
  94. if entry.type == "subscription":
  95. if not excluded:
  96. capital_after_sub = (
  97. ongoing_capital_sub + entry.total_amount_line
  98. )
  99. else:
  100. capital_after_sub = ongoing_capital_sub
  101. line_vals["capital_before_sub"] = ongoing_capital_sub
  102. line_vals["capital_after_sub"] = capital_after_sub
  103. line_vals["capital_limit"] = self.tax_shelter_capital_limit
  104. if (
  105. ongoing_capital_sub < self.tax_shelter_capital_limit
  106. and not excluded
  107. ):
  108. line_vals["tax_shelter"] = True
  109. return line_vals
  110. def _compute_certificates(self, entries, partner_certificate):
  111. ongoing_capital_sub = 0.0
  112. for entry in entries:
  113. certificate = partner_certificate.get(entry.partner_id.id, False)
  114. if not certificate:
  115. # create a certificate for this cooperator
  116. cert_vals = {}
  117. cert_vals["declaration_id"] = self.id
  118. cert_vals["partner_id"] = entry.partner_id.id
  119. cert_vals[
  120. "cooperator_number"
  121. ] = entry.partner_id.cooperator_register_number
  122. certificate = self.env["tax.shelter.certificate"].create(
  123. cert_vals
  124. )
  125. partner_certificate[entry.partner_id.id] = certificate
  126. excluded = self._excluded_from_declaration(entry)
  127. line_vals = self._prepare_line(
  128. certificate, entry, ongoing_capital_sub, excluded
  129. )
  130. certificate.write({"lines": [(0, 0, line_vals)]})
  131. if entry.type == "subscription" and not excluded:
  132. ongoing_capital_sub += entry.total_amount_line
  133. return partner_certificate
  134. @api.multi
  135. def compute_declaration(self):
  136. self.ensure_one()
  137. entries = self.env["subscription.register"].search(
  138. [
  139. ("partner_id.is_company", "=", False),
  140. ("date", "<=", self.date_to),
  141. ("type", "in", ["subscription", "sell_back", "transfer"]),
  142. ]
  143. )
  144. subscriptions = entries.filtered(
  145. lambda r: r.type == "subscription" and r.date < self.date_from
  146. ) # noqa
  147. cap_prev_sub = 0.0
  148. for subscription in subscriptions:
  149. cap_prev_sub += subscription.total_amount_line
  150. self.previously_subscribed_capital = cap_prev_sub
  151. partner_cert = {}
  152. partner_cert = self._compute_certificates(entries, partner_cert)
  153. self.state = "computed"
  154. @api.multi
  155. def validate_declaration(self):
  156. self.ensure_one()
  157. self.tax_shelter_certificates.write({"state": "validated"})
  158. self.state = "validated"
  159. @api.multi
  160. def reset_declaration(self):
  161. self.ensure_one()
  162. if not self.state == "validated":
  163. self.tax_shelter_certificates.unlink()
  164. self.state = "draft"
  165. class TaxShelterCertificate(models.Model):
  166. _name = "tax.shelter.certificate"
  167. _inherit = ["portal.mixin"]
  168. _description = "Tax Shelter Certificate"
  169. _order = "cooperator_number asc"
  170. cooperator_number = fields.Integer(
  171. string="Cooperator number", required=True, readonly=True
  172. )
  173. partner_id = fields.Many2one(
  174. "res.partner", string="Cooperator", required=True, readonly=True
  175. )
  176. state = fields.Selection(
  177. [
  178. ("draft", "Draft"),
  179. ("validated", "Validated"),
  180. ("no_eligible", "No eligible"),
  181. ("sent", "Sent"),
  182. ],
  183. string="State",
  184. required=True,
  185. default="draft",
  186. )
  187. declaration_id = fields.Many2one(
  188. "tax.shelter.declaration",
  189. string="Declaration",
  190. required=True,
  191. readonly=True,
  192. ondelete="restrict",
  193. )
  194. lines = fields.One2many(
  195. "certificate.line",
  196. "tax_shelter_certificate",
  197. string="Certificate lines",
  198. readonly=True,
  199. )
  200. previously_subscribed_lines = fields.One2many(
  201. compute="_compute_certificate_lines",
  202. comodel_name="certificate.line",
  203. string="Previously Subscribed lines",
  204. readonly=True,
  205. )
  206. previously_subscribed_eligible_lines = fields.One2many(
  207. compute="_compute_certificate_lines",
  208. comodel_name="certificate.line",
  209. string="Previously Subscribed eligible lines",
  210. readonly=True,
  211. )
  212. subscribed_lines = fields.One2many(
  213. compute="_compute_certificate_lines",
  214. comodel_name="certificate.line",
  215. string="Shares subscribed",
  216. readonly=True,
  217. )
  218. resold_lines = fields.One2many(
  219. compute="_compute_certificate_lines",
  220. comodel_name="certificate.line",
  221. string="Shares resold",
  222. readonly=True,
  223. )
  224. transfered_lines = fields.One2many(
  225. compute="_compute_certificate_lines",
  226. comodel_name="certificate.line",
  227. string="Shares transfered",
  228. readonly=True,
  229. )
  230. total_amount_previously_subscribed = fields.Float(
  231. compute="_compute_amounts", string="Total previously subscribed"
  232. )
  233. total_amount_eligible_previously_subscribed = fields.Float(
  234. compute="_compute_amounts",
  235. string="Total eligible previously subscribed",
  236. )
  237. total_amount_subscribed = fields.Float(
  238. compute="_compute_amounts", string="Total subscribed"
  239. )
  240. total_amount_eligible = fields.Float(
  241. compute="_compute_amounts",
  242. string="Total amount eligible To Tax shelter",
  243. )
  244. total_amount_resold = fields.Float(
  245. compute="_compute_amounts", string="Total resold"
  246. )
  247. total_amount_transfered = fields.Float(
  248. compute="_compute_amounts", string="Total transfered"
  249. )
  250. total_amount = fields.Float(
  251. compute="_compute_amounts", string="Total", readonly=True
  252. )
  253. company_id = fields.Many2one(
  254. related="declaration_id.company_id", string="Company"
  255. )
  256. def _compute_access_url(self):
  257. super()._compute_access_url()
  258. for certificate in self:
  259. certificate.access_url = "/my/tax_shelter_certificates/%s" % (
  260. certificate.id
  261. )
  262. def generate_pdf_report(self, report_type):
  263. report, name = REPORT_DIC[report_type]
  264. report = self.env.ref(report).render_qweb_pdf(self.id)[0]
  265. report = base64.b64encode(report)
  266. report_name = (
  267. self.partner_id.name
  268. + " "
  269. + name
  270. + " "
  271. + self.declaration_id.name
  272. + ".pdf"
  273. )
  274. return (report_name, report)
  275. def generate_certificates_report(self):
  276. attachments = []
  277. if self.total_amount_eligible > 0:
  278. attachments.append(self.generate_pdf_report("subscription"))
  279. if self.partner_id.total_value > 0:
  280. attachments.append(self.generate_pdf_report("shares"))
  281. # if self.total_amount_resold > 0 or self.total_amount_transfered > 0:
  282. # TODO
  283. return attachments
  284. @api.multi
  285. def send_certificates(self):
  286. tax_shelter_mail_template = self.env.ref(
  287. "easy_my_coop_taxshelter_report.email_template_tax_shelter_certificate",
  288. False,
  289. )
  290. for certificate in self:
  291. if (
  292. certificate.total_amount_eligible
  293. + certificate.total_amount_eligible_previously_subscribed
  294. > 0
  295. ):
  296. attachments = certificate.generate_certificates_report()
  297. if len(attachments) > 0:
  298. tax_shelter_mail_template.send_mail_with_multiple_attachments(
  299. certificate.id, attachments, True
  300. )
  301. certificate.state = "sent"
  302. else:
  303. certificate.state = "no_eligible"
  304. # pylint: disable=invalid-commit
  305. # fixme while you're here, please fix the query
  306. # to pass pylint invalid-commit
  307. # Use of cr.commit() directly is dangerous
  308. # More info https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#never-commit-the-transaction # noqa
  309. # Note: c'est n'est pas executé par du rpc-client mais via un
  310. # cron. En sachant que l'on ne veut pas faire de roll back de
  311. # toute la transaction parce que justement des mails sont
  312. # envoyés. Et on ne peut pas rollbacker des emails envoyés ici
  313. # c'est un rollback qui rendre le processus métier inconsistant
  314. # sachant que chaque ligne à son propre état et est indépendante
  315. # du statut de la déclaration tax shelter dont elle dépend
  316. self.env.cr.commit()
  317. @api.multi
  318. def print_subscription_certificate(self):
  319. self.ensure_one()
  320. report, name = REPORT_DIC["subscription"]
  321. return self.env.ref(report).report_action(self)
  322. @api.multi
  323. def print_shares_certificate(self):
  324. self.ensure_one()
  325. report, name = REPORT_DIC["shares"]
  326. return self.env.ref(report).report_action(self)
  327. @api.multi
  328. def _compute_amounts(self):
  329. for certificate in self:
  330. total_amount_previously_subscribed = 0
  331. total_amount_previously_eligible = 0
  332. total_amount_subscribed = 0
  333. total_amount_elligible = 0
  334. total_amount_transfered = 0
  335. total_amount_resold = 0
  336. for line in certificate.subscribed_lines:
  337. total_amount_subscribed += line.amount_subscribed
  338. total_amount_elligible += line.amount_subscribed_eligible
  339. certificate.total_amount_subscribed = total_amount_subscribed
  340. certificate.total_amount_eligible = total_amount_elligible
  341. for line in certificate.previously_subscribed_eligible_lines:
  342. total_amount_previously_eligible += (
  343. line.amount_subscribed_eligible
  344. )
  345. certificate.total_amount_eligible_previously_subscribed = (
  346. total_amount_previously_eligible
  347. )
  348. for line in certificate.previously_subscribed_lines:
  349. total_amount_previously_subscribed += line.amount_subscribed
  350. certificate.total_amount_previously_subscribed = (
  351. total_amount_previously_subscribed
  352. )
  353. for line in certificate.transfered_lines:
  354. total_amount_transfered += line.amount_transfered
  355. certificate.total_amount_transfered = total_amount_transfered
  356. for line in certificate.resold_lines:
  357. total_amount_resold += line.amount_resold
  358. certificate.total_amount_resold = total_amount_resold
  359. certificate.total_amount = (
  360. certificate.total_amount_previously_subscribed
  361. + certificate.total_amount_subscribed
  362. + certificate.total_amount_resold
  363. + certificate.total_amount_transfered
  364. )
  365. @api.depends("lines")
  366. def _compute_certificate_lines(self):
  367. for certificate in self:
  368. certificate.previously_subscribed_lines = certificate.lines.filtered(
  369. lambda r: r.type == "subscribed"
  370. and r.transaction_date < certificate.declaration_id.date_from
  371. )
  372. certificate.previously_subscribed_eligible_lines = certificate.lines.filtered( # noqa
  373. lambda r: r.type == "subscribed"
  374. and r.transaction_date < certificate.declaration_id.date_from
  375. and r.tax_shelter
  376. )
  377. certificate.subscribed_lines = certificate.lines.filtered(
  378. lambda r: r.type == "subscribed"
  379. and r.transaction_date >= certificate.declaration_id.date_from
  380. and r.transaction_date <= certificate.declaration_id.date_to
  381. )
  382. certificate.resold_lines = certificate.lines.filtered(
  383. lambda r: r.type == "resold"
  384. and r.transaction_date >= certificate.declaration_id.date_from
  385. and r.transaction_date <= certificate.declaration_id.date_to
  386. )
  387. certificate.transfered_lines = certificate.lines.filtered(
  388. lambda r: r.type == "transfered"
  389. and r.transaction_date >= certificate.declaration_id.date_from
  390. and r.transaction_date <= certificate.declaration_id.date_to
  391. )
  392. @api.model
  393. def batch_send_tax_shelter_certificate(self):
  394. certificates = self.search([("state", "=", "validated")], limit=80)
  395. certificates.send_certificates()
  396. class TaxShelterCertificateLine(models.Model):
  397. _name = "certificate.line"
  398. _description = "Tax Shelter Certificate Line"
  399. declaration_id = fields.Many2one(
  400. related="tax_shelter_certificate.declaration_id", string="Declaration"
  401. )
  402. tax_shelter_certificate = fields.Many2one(
  403. "tax.shelter.certificate",
  404. string="Tax shelter certificate",
  405. ondelete="cascade",
  406. required=True,
  407. )
  408. share_type = fields.Many2one(
  409. "product.product", string="Share type", required=True, readonly=True
  410. )
  411. share_unit_price = fields.Float(
  412. string="Share price", required=True, readonly=True
  413. )
  414. quantity = fields.Integer(
  415. string="Number of shares", required=True, readonly=True
  416. )
  417. transaction_date = fields.Date(string="Transaction date")
  418. tax_shelter = fields.Boolean(string="Tax shelter eligible", readonly=True)
  419. type = fields.Selection(
  420. [
  421. ("subscribed", "Subscribed"),
  422. ("resold", "Resold"),
  423. ("transfered", "Transfered"),
  424. ("kept", "Kept"),
  425. ],
  426. required=True,
  427. readonly=True,
  428. )
  429. amount_subscribed = fields.Float(
  430. compute="_compute_totals", string="Amount subscribed", store=True
  431. )
  432. amount_subscribed_eligible = fields.Float(
  433. compute="_compute_totals",
  434. string="Amount subscribed eligible",
  435. store=True,
  436. )
  437. amount_resold = fields.Float(
  438. compute="_compute_totals", string="Amount resold", store=True
  439. )
  440. amount_transfered = fields.Float(
  441. compute="_compute_totals", string="Amount transfered", store=True
  442. )
  443. share_short_name = fields.Char(string="Share type name", readonly=True)
  444. capital_before_sub = fields.Float(
  445. string="Capital before subscription", readonly=True
  446. )
  447. capital_after_sub = fields.Float(
  448. string="Capital after subscription", readonly=True
  449. )
  450. capital_limit = fields.Float(string="Capital limit", readonly=True)
  451. @api.multi
  452. @api.depends("quantity", "share_unit_price")
  453. def _compute_totals(self):
  454. for line in self:
  455. if line.type == "subscribed":
  456. line.amount_subscribed = line.share_unit_price * line.quantity
  457. if line.type == "subscribed" and line.tax_shelter:
  458. if (
  459. line.capital_before_sub < line.capital_limit
  460. and line.capital_after_sub >= line.capital_limit
  461. ):
  462. line.amount_subscribed_eligible = (
  463. line.capital_limit - line.capital_before_sub
  464. )
  465. elif (
  466. line.capital_before_sub < line.capital_limit
  467. and line.capital_after_sub <= line.capital_limit
  468. ):
  469. line.amount_subscribed_eligible = (
  470. line.share_unit_price * line.quantity
  471. )
  472. elif line.capital_before_sub >= line.capital_limit:
  473. line.amount_subscribed_eligible = 0
  474. if line.type == "resold":
  475. line.amount_resold = line.share_unit_price * -(line.quantity)
  476. if line.type == "transfered":
  477. line.amount_transfered = line.share_unit_price * -(
  478. line.quantity
  479. )