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.

463 lines
17 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%")], string="Tax Shelter percentage", required=True
  35. )
  36. state = fields.Selection(
  37. [("draft", "Draft"), ("computed", "Computed"), ("validated", "Validated")],
  38. string="State",
  39. required=True,
  40. default="draft",
  41. )
  42. company_id = fields.Many2one(
  43. "res.company",
  44. string="Company",
  45. required=True,
  46. change_default=True,
  47. readonly=True,
  48. default=lambda self: self.env["res.company"]._company_default_get(),
  49. )
  50. tax_shelter_capital_limit = fields.Float(
  51. string="Tax shelter capital limit", required=True
  52. )
  53. previously_subscribed_capital = fields.Float(
  54. String="Capital previously subscribed", readonly=True
  55. )
  56. excluded_cooperator = fields.Many2many(
  57. "res.partner",
  58. string="Excluded cooperator",
  59. domain=[("cooperator", "=", True)],
  60. help="If these cooperator have"
  61. " subscribed share during the time"
  62. " frame of this Tax Shelter "
  63. "Declaration. They will be marked "
  64. "as non eligible",
  65. )
  66. def _excluded_from_declaration(self, entry):
  67. if entry.date >= self.date_from and entry.date <= self.date_to:
  68. declaration = self
  69. else:
  70. declaration = self.search(
  71. [("date_from", "<=", entry.date), ("date_to", ">=", entry.date)]
  72. )
  73. if entry.partner_id.id in declaration.excluded_cooperator.ids:
  74. return True
  75. return False
  76. def _prepare_line(self, certificate, entry, ongoing_capital_sub, excluded):
  77. line_vals = {}
  78. line_vals["tax_shelter_certificate"] = certificate.id
  79. line_vals["share_type"] = entry.share_product_id.id
  80. line_vals["share_short_name"] = entry.share_short_name
  81. line_vals["share_unit_price"] = entry.share_unit_price
  82. line_vals["quantity"] = entry.quantity
  83. line_vals["transaction_date"] = entry.date
  84. line_vals["type"] = TYPE_MAP[entry.type]
  85. if entry.type == "subscription":
  86. if not excluded:
  87. capital_after_sub = ongoing_capital_sub + entry.total_amount_line
  88. else:
  89. capital_after_sub = ongoing_capital_sub
  90. line_vals["capital_before_sub"] = ongoing_capital_sub
  91. line_vals["capital_after_sub"] = capital_after_sub
  92. line_vals["capital_limit"] = self.tax_shelter_capital_limit
  93. if ongoing_capital_sub < self.tax_shelter_capital_limit and not excluded:
  94. line_vals["tax_shelter"] = True
  95. return line_vals
  96. def _compute_certificates(self, entries, partner_certificate):
  97. ongoing_capital_sub = 0.0
  98. for entry in entries:
  99. certificate = partner_certificate.get(entry.partner_id.id, False)
  100. if not certificate:
  101. # create a certificate for this cooperator
  102. cert_vals = {}
  103. cert_vals["declaration_id"] = self.id
  104. cert_vals["partner_id"] = entry.partner_id.id
  105. cert_vals[
  106. "cooperator_number"
  107. ] = entry.partner_id.cooperator_register_number
  108. certificate = self.env["tax.shelter.certificate"].create(cert_vals)
  109. partner_certificate[entry.partner_id.id] = certificate
  110. excluded = self._excluded_from_declaration(entry)
  111. line_vals = self._prepare_line(
  112. certificate, entry, ongoing_capital_sub, excluded
  113. )
  114. certificate.write({"lines": [(0, 0, line_vals)]})
  115. if entry.type == "subscription" and not excluded:
  116. ongoing_capital_sub += entry.total_amount_line
  117. return partner_certificate
  118. @api.one
  119. def compute_declaration(self):
  120. entries = self.env["subscription.register"].search(
  121. [
  122. ("partner_id.is_company", "=", False),
  123. ("date", "<=", self.date_to),
  124. ("type", "in", ["subscription", "sell_back", "transfer"]),
  125. ]
  126. )
  127. subscriptions = entries.filtered(
  128. (lambda r: r.type == "subscription" and r.date < self.date_from)
  129. ) # noqa
  130. cap_prev_sub = 0.0
  131. for subscription in subscriptions:
  132. cap_prev_sub += subscription.total_amount_line
  133. self.previously_subscribed_capital = cap_prev_sub
  134. partner_cert = {}
  135. partner_cert = self._compute_certificates(entries, partner_cert)
  136. self.state = "computed"
  137. @api.one
  138. def validate_declaration(self):
  139. self.tax_shelter_certificates.write({"state": "validated"})
  140. self.state = "validated"
  141. @api.one
  142. def reset_declaration(self):
  143. if not self.state == "validated":
  144. self.tax_shelter_certificates.unlink()
  145. self.state = "draft"
  146. class TaxShelterCertificate(models.Model):
  147. _name = "tax.shelter.certificate"
  148. _description = "Tax Shelter Certificate"
  149. _order = "cooperator_number asc"
  150. cooperator_number = fields.Integer(
  151. string="Cooperator number", required=True, readonly=True
  152. )
  153. partner_id = fields.Many2one(
  154. "res.partner", string="Cooperator", required=True, readonly=True
  155. )
  156. state = fields.Selection(
  157. [
  158. ("draft", "Draft"),
  159. ("validated", "Validated"),
  160. ("no_eligible", "No eligible"),
  161. ("sent", "Sent"),
  162. ],
  163. string="State",
  164. required=True,
  165. default="draft",
  166. )
  167. declaration_id = fields.Many2one(
  168. "tax.shelter.declaration",
  169. string="Declaration",
  170. required=True,
  171. readonly=True,
  172. ondelete="restrict",
  173. )
  174. lines = fields.One2many(
  175. "certificate.line",
  176. "tax_shelter_certificate",
  177. string="Certificate lines",
  178. readonly=True,
  179. )
  180. previously_subscribed_lines = fields.One2many(
  181. compute="_compute_certificate_lines",
  182. comodel_name="certificate.line",
  183. string="Previously Subscribed lines",
  184. readonly=True,
  185. )
  186. previously_subscribed_eligible_lines = fields.One2many(
  187. compute="_compute_certificate_lines",
  188. comodel_name="certificate.line",
  189. string="Previously Subscribed eligible lines",
  190. readonly=True,
  191. )
  192. subscribed_lines = fields.One2many(
  193. compute="_compute_certificate_lines",
  194. comodel_name="certificate.line",
  195. string="Shares subscribed",
  196. readonly=True,
  197. )
  198. resold_lines = fields.One2many(
  199. compute="_compute_certificate_lines",
  200. comodel_name="certificate.line",
  201. string="Shares resold",
  202. readonly=True,
  203. )
  204. transfered_lines = fields.One2many(
  205. compute="_compute_certificate_lines",
  206. comodel_name="certificate.line",
  207. string="Shares transfered",
  208. readonly=True,
  209. )
  210. total_amount_previously_subscribed = fields.Float(
  211. compute="_compute_amounts", string="Total previously subscribed"
  212. )
  213. total_amount_eligible_previously_subscribed = fields.Float(
  214. compute="_compute_amounts", string="Total eligible previously subscribed"
  215. )
  216. total_amount_subscribed = fields.Float(
  217. compute="_compute_amounts", string="Total subscribed"
  218. )
  219. total_amount_eligible = fields.Float(
  220. compute="_compute_amounts", string="Total amount eligible To Tax shelter"
  221. )
  222. total_amount_resold = fields.Float(
  223. compute="_compute_amounts", string="Total resold"
  224. )
  225. total_amount_transfered = fields.Float(
  226. compute="_compute_amounts", string="Total transfered"
  227. )
  228. total_amount = fields.Float(
  229. compute="_compute_amounts", string="Total", readonly=True
  230. )
  231. company_id = fields.Many2one(related="declaration_id.company_id", string="Company")
  232. def generate_pdf_report(self, report_type):
  233. report, name = REPORT_DIC[report_type]
  234. report = self.env.ref(report).render_qweb_pdf(self)
  235. report = base64.b64encode(report)
  236. report_name = (
  237. self.partner_id.name + " " + name + " " + self.declaration_id.name + ".pdf"
  238. )
  239. return (report_name, report)
  240. def generate_certificates_report(self):
  241. attachments = []
  242. if self.total_amount_eligible > 0:
  243. attachments.append(self.generate_pdf_report("subscription"))
  244. if self.partner_id.total_value > 0:
  245. attachments.append(self.generate_pdf_report("shares"))
  246. # if self.total_amount_resold > 0 or self.total_amount_transfered > 0:
  247. # TODO
  248. return attachments
  249. @api.multi
  250. def send_certificates(self):
  251. tax_shelter_mail_template = self.env.ref(
  252. "easy_my_coop_taxshelter_report.email_template_tax_shelter_certificate",
  253. False,
  254. )
  255. for certificate in self:
  256. if (
  257. certificate.total_amount_eligible
  258. + certificate.total_amount_eligible_previously_subscribed
  259. > 0
  260. ):
  261. attachments = certificate.generate_certificates_report()
  262. if len(attachments) > 0:
  263. tax_shelter_mail_template.send_mail_with_multiple_attachments(
  264. certificate.id, attachments, True
  265. )
  266. certificate.state = "sent"
  267. else:
  268. certificate.state = "no_eligible"
  269. self.env.cr.commit()
  270. @api.multi
  271. def print_subscription_certificate(self):
  272. self.ensure_one()
  273. report, name = REPORT_DIC["subscription"]
  274. return self.env.ref(report).report_action(self)
  275. @api.multi
  276. def print_shares_certificate(self):
  277. self.ensure_one()
  278. report, name = REPORT_DIC["shares"]
  279. return self.env.ref(report).report_action(self)
  280. @api.multi
  281. def _compute_amounts(self):
  282. for certificate in self:
  283. total_amount_previously_subscribed = 0
  284. total_amount_previously_eligible = 0
  285. total_amount_subscribed = 0
  286. total_amount_elligible = 0
  287. total_amount_transfered = 0
  288. total_amount_resold = 0
  289. for line in certificate.subscribed_lines:
  290. total_amount_subscribed += line.amount_subscribed
  291. total_amount_elligible += line.amount_subscribed_eligible
  292. certificate.total_amount_subscribed = total_amount_subscribed
  293. certificate.total_amount_eligible = total_amount_elligible
  294. for line in certificate.previously_subscribed_eligible_lines:
  295. total_amount_previously_eligible += line.amount_subscribed_eligible
  296. certificate.total_amount_eligible_previously_subscribed = (
  297. total_amount_previously_eligible
  298. )
  299. for line in certificate.previously_subscribed_lines:
  300. total_amount_previously_subscribed += line.amount_subscribed
  301. certificate.total_amount_previously_subscribed = (
  302. total_amount_previously_subscribed
  303. )
  304. for line in certificate.transfered_lines:
  305. total_amount_transfered += line.amount_transfered
  306. certificate.total_amount_transfered = total_amount_transfered
  307. for line in certificate.resold_lines:
  308. total_amount_resold += line.amount_resold
  309. certificate.total_amount_resold = total_amount_resold
  310. certificate.total_amount = (
  311. certificate.total_amount_previously_subscribed
  312. + certificate.total_amount_subscribed
  313. + certificate.total_amount_resold
  314. + certificate.total_amount_transfered
  315. )
  316. @api.depends("lines")
  317. def _compute_certificate_lines(self):
  318. for certificate in self:
  319. certificate.previously_subscribed_lines = certificate.lines.filtered(
  320. lambda r: r.type == "subscribed"
  321. and r.transaction_date < certificate.declaration_id.date_from
  322. )
  323. certificate.previously_subscribed_eligible_lines = certificate.lines.filtered(
  324. lambda r: r.type == "subscribed"
  325. and r.transaction_date < certificate.declaration_id.date_from
  326. and r.tax_shelter
  327. )
  328. certificate.subscribed_lines = certificate.lines.filtered(
  329. lambda r: r.type == "subscribed"
  330. and r.transaction_date >= certificate.declaration_id.date_from
  331. and r.transaction_date <= certificate.declaration_id.date_to
  332. )
  333. certificate.resold_lines = certificate.lines.filtered(
  334. lambda r: r.type == "resold"
  335. and r.transaction_date >= certificate.declaration_id.date_from
  336. and r.transaction_date <= certificate.declaration_id.date_to
  337. )
  338. certificate.transfered_lines = certificate.lines.filtered(
  339. lambda r: r.type == "transfered"
  340. and r.transaction_date >= certificate.declaration_id.date_from
  341. and r.transaction_date <= certificate.declaration_id.date_to
  342. )
  343. @api.model
  344. def batch_send_tax_shelter_certificate(self):
  345. certificates = self.search([("state", "=", "validated")], limit=80)
  346. certificates.send_certificates()
  347. class TaxShelterCertificateLine(models.Model):
  348. _name = "certificate.line"
  349. _description = "Tax Shelter Certificate Line"
  350. declaration_id = fields.Many2one(
  351. related="tax_shelter_certificate.declaration_id", string="Declaration"
  352. )
  353. tax_shelter_certificate = fields.Many2one(
  354. "tax.shelter.certificate",
  355. string="Tax shelter certificate",
  356. ondelete="cascade",
  357. required=True,
  358. )
  359. share_type = fields.Many2one(
  360. "product.product", string="Share type", required=True, readonly=True
  361. )
  362. share_unit_price = fields.Float(string="Share price", required=True, readonly=True)
  363. quantity = fields.Integer(string="Number of shares", required=True, readonly=True)
  364. transaction_date = fields.Date(string="Transaction date")
  365. tax_shelter = fields.Boolean(string="Tax shelter eligible", readonly=True)
  366. type = fields.Selection(
  367. [
  368. ("subscribed", "Subscribed"),
  369. ("resold", "Resold"),
  370. ("transfered", "Transfered"),
  371. ("kept", "Kept"),
  372. ],
  373. required=True,
  374. readonly=True,
  375. )
  376. amount_subscribed = fields.Float(
  377. compute="_compute_totals", string="Amount subscribed", store=True
  378. )
  379. amount_subscribed_eligible = fields.Float(
  380. compute="_compute_totals", string="Amount subscribed eligible", store=True
  381. )
  382. amount_resold = fields.Float(
  383. compute="_compute_totals", string="Amount resold", store=True
  384. )
  385. amount_transfered = fields.Float(
  386. compute="_compute_totals", string="Amount transfered", store=True
  387. )
  388. share_short_name = fields.Char(string="Share type name", readonly=True)
  389. capital_before_sub = fields.Float(
  390. string="Capital before subscription", readonly=True
  391. )
  392. capital_after_sub = fields.Float(string="Capital after subscription", readonly=True)
  393. capital_limit = fields.Float(string="Capital limit", readonly=True)
  394. @api.multi
  395. @api.depends("quantity", "share_unit_price")
  396. def _compute_totals(self):
  397. for line in self:
  398. if line.type == "subscribed":
  399. line.amount_subscribed = line.share_unit_price * line.quantity
  400. if line.type == "subscribed" and line.tax_shelter:
  401. if (
  402. line.capital_before_sub < line.capital_limit
  403. and line.capital_after_sub >= line.capital_limit
  404. ):
  405. line.amount_subscribed_eligible = (
  406. line.capital_limit - line.capital_before_sub
  407. )
  408. elif (
  409. line.capital_before_sub < line.capital_limit
  410. and line.capital_after_sub <= line.capital_limit
  411. ):
  412. line.amount_subscribed_eligible = (
  413. line.share_unit_price * line.quantity
  414. )
  415. elif line.capital_before_sub >= line.capital_limit:
  416. line.amount_subscribed_eligible = 0
  417. if line.type == "resold":
  418. line.amount_resold = line.share_unit_price * -(line.quantity)
  419. if line.type == "transfered":
  420. line.amount_transfered = line.share_unit_price * -(line.quantity)