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.

503 lines
18 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. _description = "Tax Shelter Certificate"
  168. _order = "cooperator_number asc"
  169. cooperator_number = fields.Integer(
  170. string="Cooperator number", required=True, readonly=True
  171. )
  172. partner_id = fields.Many2one(
  173. "res.partner", string="Cooperator", required=True, readonly=True
  174. )
  175. state = fields.Selection(
  176. [
  177. ("draft", "Draft"),
  178. ("validated", "Validated"),
  179. ("no_eligible", "No eligible"),
  180. ("sent", "Sent"),
  181. ],
  182. string="State",
  183. required=True,
  184. default="draft",
  185. )
  186. declaration_id = fields.Many2one(
  187. "tax.shelter.declaration",
  188. string="Declaration",
  189. required=True,
  190. readonly=True,
  191. ondelete="restrict",
  192. )
  193. lines = fields.One2many(
  194. "certificate.line",
  195. "tax_shelter_certificate",
  196. string="Certificate lines",
  197. readonly=True,
  198. )
  199. previously_subscribed_lines = fields.One2many(
  200. compute="_compute_certificate_lines",
  201. comodel_name="certificate.line",
  202. string="Previously Subscribed lines",
  203. readonly=True,
  204. )
  205. previously_subscribed_eligible_lines = fields.One2many(
  206. compute="_compute_certificate_lines",
  207. comodel_name="certificate.line",
  208. string="Previously Subscribed eligible lines",
  209. readonly=True,
  210. )
  211. subscribed_lines = fields.One2many(
  212. compute="_compute_certificate_lines",
  213. comodel_name="certificate.line",
  214. string="Shares subscribed",
  215. readonly=True,
  216. )
  217. resold_lines = fields.One2many(
  218. compute="_compute_certificate_lines",
  219. comodel_name="certificate.line",
  220. string="Shares resold",
  221. readonly=True,
  222. )
  223. transfered_lines = fields.One2many(
  224. compute="_compute_certificate_lines",
  225. comodel_name="certificate.line",
  226. string="Shares transfered",
  227. readonly=True,
  228. )
  229. total_amount_previously_subscribed = fields.Float(
  230. compute="_compute_amounts", string="Total previously subscribed"
  231. )
  232. total_amount_eligible_previously_subscribed = fields.Float(
  233. compute="_compute_amounts",
  234. string="Total eligible previously subscribed",
  235. )
  236. total_amount_subscribed = fields.Float(
  237. compute="_compute_amounts", string="Total subscribed"
  238. )
  239. total_amount_eligible = fields.Float(
  240. compute="_compute_amounts",
  241. string="Total amount eligible To Tax shelter",
  242. )
  243. total_amount_resold = fields.Float(
  244. compute="_compute_amounts", string="Total resold"
  245. )
  246. total_amount_transfered = fields.Float(
  247. compute="_compute_amounts", string="Total transfered"
  248. )
  249. total_amount = fields.Float(
  250. compute="_compute_amounts", string="Total", readonly=True
  251. )
  252. company_id = fields.Many2one(
  253. related="declaration_id.company_id", string="Company"
  254. )
  255. def generate_pdf_report(self, report_type):
  256. report, name = REPORT_DIC[report_type]
  257. report = self.env.ref(report).render_qweb_pdf(self.id)[0]
  258. report = base64.b64encode(report)
  259. report_name = (
  260. self.partner_id.name
  261. + " "
  262. + name
  263. + " "
  264. + self.declaration_id.name
  265. + ".pdf"
  266. )
  267. return (report_name, report)
  268. def generate_certificates_report(self):
  269. attachments = []
  270. if self.total_amount_eligible > 0:
  271. attachments.append(self.generate_pdf_report("subscription"))
  272. if self.partner_id.total_value > 0:
  273. attachments.append(self.generate_pdf_report("shares"))
  274. # if self.total_amount_resold > 0 or self.total_amount_transfered > 0:
  275. # TODO
  276. return attachments
  277. @api.multi
  278. def send_certificates(self):
  279. tax_shelter_mail_template = self.env.ref(
  280. "easy_my_coop_taxshelter_report.email_template_tax_shelter_certificate",
  281. False,
  282. )
  283. for certificate in self:
  284. if (
  285. certificate.total_amount_eligible
  286. + certificate.total_amount_eligible_previously_subscribed
  287. > 0
  288. ):
  289. attachments = certificate.generate_certificates_report()
  290. if len(attachments) > 0:
  291. tax_shelter_mail_template.send_mail_with_multiple_attachments(
  292. certificate.id, attachments, True
  293. )
  294. certificate.state = "sent"
  295. else:
  296. certificate.state = "no_eligible"
  297. self.env.cr.commit()
  298. @api.multi
  299. def print_subscription_certificate(self):
  300. self.ensure_one()
  301. report, name = REPORT_DIC["subscription"]
  302. return self.env.ref(report).report_action(self)
  303. @api.multi
  304. def print_shares_certificate(self):
  305. self.ensure_one()
  306. report, name = REPORT_DIC["shares"]
  307. return self.env.ref(report).report_action(self)
  308. @api.multi
  309. def _compute_amounts(self):
  310. for certificate in self:
  311. total_amount_previously_subscribed = 0
  312. total_amount_previously_eligible = 0
  313. total_amount_subscribed = 0
  314. total_amount_elligible = 0
  315. total_amount_transfered = 0
  316. total_amount_resold = 0
  317. for line in certificate.subscribed_lines:
  318. total_amount_subscribed += line.amount_subscribed
  319. total_amount_elligible += line.amount_subscribed_eligible
  320. certificate.total_amount_subscribed = total_amount_subscribed
  321. certificate.total_amount_eligible = total_amount_elligible
  322. for line in certificate.previously_subscribed_eligible_lines:
  323. total_amount_previously_eligible += (
  324. line.amount_subscribed_eligible
  325. )
  326. certificate.total_amount_eligible_previously_subscribed = (
  327. total_amount_previously_eligible
  328. )
  329. for line in certificate.previously_subscribed_lines:
  330. total_amount_previously_subscribed += line.amount_subscribed
  331. certificate.total_amount_previously_subscribed = (
  332. total_amount_previously_subscribed
  333. )
  334. for line in certificate.transfered_lines:
  335. total_amount_transfered += line.amount_transfered
  336. certificate.total_amount_transfered = total_amount_transfered
  337. for line in certificate.resold_lines:
  338. total_amount_resold += line.amount_resold
  339. certificate.total_amount_resold = total_amount_resold
  340. certificate.total_amount = (
  341. certificate.total_amount_previously_subscribed
  342. + certificate.total_amount_subscribed
  343. + certificate.total_amount_resold
  344. + certificate.total_amount_transfered
  345. )
  346. @api.depends("lines")
  347. def _compute_certificate_lines(self):
  348. for certificate in self:
  349. certificate.previously_subscribed_lines = certificate.lines.filtered(
  350. lambda r: r.type == "subscribed"
  351. and r.transaction_date < certificate.declaration_id.date_from
  352. )
  353. certificate.previously_subscribed_eligible_lines = certificate.lines.filtered( # noqa
  354. lambda r: r.type == "subscribed"
  355. and r.transaction_date < certificate.declaration_id.date_from
  356. and r.tax_shelter
  357. )
  358. certificate.subscribed_lines = certificate.lines.filtered(
  359. lambda r: r.type == "subscribed"
  360. and r.transaction_date >= certificate.declaration_id.date_from
  361. and r.transaction_date <= certificate.declaration_id.date_to
  362. )
  363. certificate.resold_lines = certificate.lines.filtered(
  364. lambda r: r.type == "resold"
  365. and r.transaction_date >= certificate.declaration_id.date_from
  366. and r.transaction_date <= certificate.declaration_id.date_to
  367. )
  368. certificate.transfered_lines = certificate.lines.filtered(
  369. lambda r: r.type == "transfered"
  370. and r.transaction_date >= certificate.declaration_id.date_from
  371. and r.transaction_date <= certificate.declaration_id.date_to
  372. )
  373. @api.model
  374. def batch_send_tax_shelter_certificate(self):
  375. certificates = self.search([("state", "=", "validated")], limit=80)
  376. certificates.send_certificates()
  377. class TaxShelterCertificateLine(models.Model):
  378. _name = "certificate.line"
  379. _description = "Tax Shelter Certificate Line"
  380. declaration_id = fields.Many2one(
  381. related="tax_shelter_certificate.declaration_id", string="Declaration"
  382. )
  383. tax_shelter_certificate = fields.Many2one(
  384. "tax.shelter.certificate",
  385. string="Tax shelter certificate",
  386. ondelete="cascade",
  387. required=True,
  388. )
  389. share_type = fields.Many2one(
  390. "product.product", string="Share type", required=True, readonly=True
  391. )
  392. share_unit_price = fields.Float(
  393. string="Share price", required=True, readonly=True
  394. )
  395. quantity = fields.Integer(
  396. string="Number of shares", required=True, readonly=True
  397. )
  398. transaction_date = fields.Date(string="Transaction date")
  399. tax_shelter = fields.Boolean(string="Tax shelter eligible", readonly=True)
  400. type = fields.Selection(
  401. [
  402. ("subscribed", "Subscribed"),
  403. ("resold", "Resold"),
  404. ("transfered", "Transfered"),
  405. ("kept", "Kept"),
  406. ],
  407. required=True,
  408. readonly=True,
  409. )
  410. amount_subscribed = fields.Float(
  411. compute="_compute_totals", string="Amount subscribed", store=True
  412. )
  413. amount_subscribed_eligible = fields.Float(
  414. compute="_compute_totals",
  415. string="Amount subscribed eligible",
  416. store=True,
  417. )
  418. amount_resold = fields.Float(
  419. compute="_compute_totals", string="Amount resold", store=True
  420. )
  421. amount_transfered = fields.Float(
  422. compute="_compute_totals", string="Amount transfered", store=True
  423. )
  424. share_short_name = fields.Char(string="Share type name", readonly=True)
  425. capital_before_sub = fields.Float(
  426. string="Capital before subscription", readonly=True
  427. )
  428. capital_after_sub = fields.Float(
  429. string="Capital after subscription", readonly=True
  430. )
  431. capital_limit = fields.Float(string="Capital limit", readonly=True)
  432. @api.multi
  433. @api.depends("quantity", "share_unit_price")
  434. def _compute_totals(self):
  435. for line in self:
  436. if line.type == "subscribed":
  437. line.amount_subscribed = line.share_unit_price * line.quantity
  438. if line.type == "subscribed" and line.tax_shelter:
  439. if (
  440. line.capital_before_sub < line.capital_limit
  441. and line.capital_after_sub >= line.capital_limit
  442. ):
  443. line.amount_subscribed_eligible = (
  444. line.capital_limit - line.capital_before_sub
  445. )
  446. elif (
  447. line.capital_before_sub < line.capital_limit
  448. and line.capital_after_sub <= line.capital_limit
  449. ):
  450. line.amount_subscribed_eligible = (
  451. line.share_unit_price * line.quantity
  452. )
  453. elif line.capital_before_sub >= line.capital_limit:
  454. line.amount_subscribed_eligible = 0
  455. if line.type == "resold":
  456. line.amount_resold = line.share_unit_price * -(line.quantity)
  457. if line.type == "transfered":
  458. line.amount_transfered = line.share_unit_price * -(
  459. line.quantity
  460. )