Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

497 lines
17KB

  1. <?php
  2. /**
  3. Copyright Guillaume Bourgeois (2018)
  4. contact@souke.fr
  5. Ce logiciel est un programme informatique servant à aider les producteurs
  6. à distribuer leur production en circuits courts.
  7. Ce logiciel est régi par la licence CeCILL soumise au droit français et
  8. respectant les principes de diffusion des logiciels libres. Vous pouvez
  9. utiliser, modifier et/ou redistribuer ce programme sous les conditions
  10. de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA
  11. sur le site "http://www.cecill.info".
  12. En contrepartie de l'accessibilité au code source et des droits de copie,
  13. de modification et de redistribution accordés par cette licence, il n'est
  14. offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons,
  15. seule une responsabilité restreinte pèse sur l'auteur du programme, le
  16. titulaire des droits patrimoniaux et les concédants successifs.
  17. A cet égard l'attention de l'utilisateur est attirée sur les risques
  18. associés au chargement, à l'utilisation, à la modification et/ou au
  19. développement et à la reproduction du logiciel par l'utilisateur étant
  20. donné sa spécificité de logiciel libre, qui peut le rendre complexe à
  21. manipuler et qui le réserve donc à des développeurs et des professionnels
  22. avertis possédant des connaissances informatiques approfondies. Les
  23. utilisateurs sont donc invités à charger et tester l'adéquation du
  24. logiciel à leurs besoins dans des conditions permettant d'assurer la
  25. sécurité de leurs systèmes et ou de leurs données et, plus généralement,
  26. à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.
  27. Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
  28. pris connaissance de la licence CeCILL, et que vous en avez accepté les
  29. termes.
  30. */
  31. namespace domain\Producer\Producer;
  32. use common\helpers\Departments;
  33. use common\helpers\Price;
  34. use domain\Document\Document\DocumentInterface;
  35. use domain\Document\Document\DocumentSolver;
  36. use domain\Order\Order\OrderRepositoryQuery;
  37. use domain\Order\OrderStatus\OrderStatus;
  38. use domain\PointSale\PointSale\PointSale;
  39. use domain\Producer\ProducerPriceRange\ProducerPriceRangeRepository;
  40. use domain\User\User\User;
  41. use domain\_\AbstractRepository;
  42. use yii\helpers\Html;
  43. class ProducerRepository extends AbstractRepository
  44. {
  45. protected ProducerRepositoryQuery $query;
  46. protected ProducerPriceRangeRepository $producerPriceRangeRepository;
  47. protected ProducerSolver $producerSolver;
  48. protected DocumentSolver $documentSolver;
  49. public function loadDependencies(): void
  50. {
  51. $this->loadQuery(ProducerRepositoryQuery::class);
  52. $this->producerPriceRangeRepository = $this->loadService(ProducerPriceRangeRepository::class);
  53. $this->producerSolver = $this->loadService(ProducerSolver::class);
  54. $this->documentSolver = $this->loadService(DocumentSolver::class);
  55. }
  56. /**
  57. * Retourne les options de base nécessaires à la fonction de recherche.
  58. *
  59. */
  60. public function getDefaultOptionsSearch(): array
  61. {
  62. return [
  63. self::WITH => ['taxRate', 'contact'],
  64. self::JOIN_WITH => [],
  65. self::ORDER_BY => 'name ASC',
  66. self::ATTRIBUTE_ID_PRODUCER => 'id'
  67. ];
  68. }
  69. public function findOneProducerById(int $id)
  70. {
  71. return $this->createQuery()
  72. ->filterById($id)
  73. ->findOne();
  74. }
  75. public function findOneProducerBySlug(string $slug)
  76. {
  77. return $this->createQuery()
  78. ->filterBySlug($slug)
  79. ->findOne();
  80. }
  81. public function queryProducersActive(string $orderByField = 'producer.name', string $orderByDirection = 'ASC')
  82. {
  83. return $this->createQuery()
  84. ->filterIsActive()
  85. ->orderBy($orderByField.' '.$orderByDirection);
  86. }
  87. /**
  88. * Retourne le compte producteur de démonstration.
  89. */
  90. public function findOneProducerDemoAccount()
  91. {
  92. return $this->createQuery()
  93. ->filterIsDemoAccount()
  94. ->findOne();
  95. }
  96. /**
  97. * Retourne la liste des établissements pour l'initialisation d'une listesélective.
  98. */
  99. public function populateProducerDropdown(bool $excludeProducerContext = false): array
  100. {
  101. $producerContext = $this->getProducerContext(false);
  102. $producers = $this->createQuery()
  103. ->filterIsActive()
  104. ->orderBy('postcode, city ASC')
  105. ->find();
  106. $departments = Departments::get();
  107. $dataProducers = [];
  108. $optionsProducers = [];
  109. foreach ($producers as $p) {
  110. if($excludeProducerContext && $producerContext && $producerContext->id != $p->id || !$excludeProducerContext) {
  111. $departmentCode = substr($p->postcode, 0, 2);
  112. if (!key_exists('d' . $departmentCode, $dataProducers) && isset($departments[$departmentCode])) {
  113. $dataProducers['d' . $departmentCode] = '<strong>' . $departments[$departmentCode] . '</strong>';
  114. $optionsProducers['d' . $departmentCode] = ['disabled' => true];
  115. }
  116. $dataProducers[$p->id] = '<span class="glyphicon glyphicon-lock"></span> ' . Html::encode(
  117. $p->name
  118. ) . ' - ' . Html::encode($p->postcode) . ' ' . Html::encode(
  119. $p->city
  120. ) . ' <span class="glyphicon glyphicon-lock"></span>';
  121. if (strlen($p->code)) {
  122. $optionsProducers[$p->id] = ['class' => 'lock'];
  123. }
  124. }
  125. }
  126. return [
  127. 'data' => $dataProducers,
  128. 'options' => $optionsProducers
  129. ];
  130. }
  131. public function getTurnoverLastMonth(Producer $producer, bool $format = false)
  132. {
  133. $period = date('Y-m', strtotime('-1 month'));
  134. return $this->getTurnover($producer, $period, false, $format);
  135. }
  136. public function getYearsWithTurnover(Producer $producer): array
  137. {
  138. $year = date('Y');
  139. $yearsArray = [];
  140. for($i = 0; $i <= 10; $i++) {
  141. if($this->hasTurnoverOverTheYear($producer, $year - $i)) {
  142. array_unshift($yearsArray, $year - $i);
  143. }
  144. }
  145. return $yearsArray;
  146. }
  147. public function hasTurnoverOverTheYear(Producer $producer, int $year): bool
  148. {
  149. for($month = 1; $month <= 12; $month++) {
  150. if($this->getTurnover($producer, $year.'-'.$month)) {
  151. return true;
  152. }
  153. }
  154. return false;
  155. }
  156. /**
  157. * Retourne le CA du producteur pour un mois donné
  158. */
  159. public function getTurnover(Producer $producer, string $period = '', bool $withTax = false, bool $format = false)
  160. {
  161. if (!$period) {
  162. $period = date('Y-m');
  163. }
  164. $dateStart = date('Y-m-31', strtotime("-1 month", strtotime($period)));
  165. $dateEnd = date('Y-m-01', strtotime("+1 month", strtotime($period)));
  166. return $this->getTurnoverByDateStartEnd($producer, $dateStart, $dateEnd, $withTax, $format);
  167. }
  168. public function getTurnoverByWeek(Producer $producer, int $year, int $week, bool $withTax = false, bool $format = false)
  169. {
  170. $date = new \DateTime();
  171. $date->setISODate($year, $week);
  172. $dateStart = $date->format('Y-m-d');
  173. $date->modify('+6 days');
  174. $dateEnd = $date->format('Y-m-d');
  175. return $this->getTurnoverByDateStartEnd($producer, $dateStart, $dateEnd, $withTax, $format);
  176. }
  177. public function getTurnoverByDateStartEnd(Producer $producer, string $dateStart, string $dateEnd, bool $withTax = false, bool $format = false)
  178. {
  179. $connection = \Yii::$app->getDb();
  180. $selectSum = 'product_order.price * product_order.quantity';
  181. if($withTax) {
  182. $selectSum .= ' * (tax_rate.value + 1)';
  183. }
  184. $command = $connection->createCommand(
  185. '
  186. SELECT SUM('.$selectSum.') AS turnover
  187. FROM `order`, product_order, distribution, tax_rate
  188. WHERE `order`.id = product_order.id_order
  189. AND '.OrderRepositoryQuery::getSqlFilterIsValid().'
  190. AND distribution.id_producer = :id_producer
  191. AND `order`.id_distribution = distribution.id
  192. AND distribution.date > :date_start
  193. AND distribution.date < :date_end
  194. AND product_order.id_tax_rate = tax_rate.id',
  195. [
  196. ':date_start' => $dateStart,
  197. ':date_end' => $dateEnd,
  198. ':id_producer' => $producer->id
  199. ]
  200. );
  201. $result = $command->queryOne();
  202. $turnover = (float) $result['turnover'];
  203. if ($format) {
  204. return number_format($turnover, 2) . ' €';
  205. } else {
  206. return $turnover;
  207. }
  208. }
  209. public function getTurnoverByNumberMonths(Producer $producer, $numberMonths = 1)
  210. {
  211. $totalTurnover = 0;
  212. for ($i = 1; $i <= $numberMonths; $i++) {
  213. $timeMonth = strtotime('-' . $i . ' month');
  214. $month = date('Y-m', $timeMonth);
  215. $totalTurnover += $this->getTurnover($producer, $month);
  216. }
  217. return $totalTurnover;
  218. }
  219. public function getAmountToBeBilledByMonth(Producer $producer, $month, bool $format = false)
  220. {
  221. $turnover = $this->getTurnover($producer, $month);
  222. return $this->producerPriceRangeRepository->getAmountToBeBilledByTurnover($turnover, $format);
  223. }
  224. public function getDatasChartTurnoverStatistics(Producer $producer, int $year, string $displayBy = 'month', bool $withTax = false)
  225. {
  226. $data = [];
  227. $dataLabels = [];
  228. $start = new \DateTime($year.'-01-01');
  229. if($year == date('Y')) {
  230. $end = new \DateTime('last day of this month');
  231. }
  232. else {
  233. $end = new \DateTime($year.'-12-31');
  234. }
  235. $interval = new \DateInterval(($displayBy == 'week') ? 'P1W' : 'P1M');
  236. $period = new \DatePeriod($start, $interval, $end);
  237. if($displayBy == 'week') {
  238. foreach ($period as $date) {
  239. $week = $date->format('W');
  240. $dataLabels[] = $week;
  241. $turnover = $this->getTurnoverByWeek($producer, $year, $date->format('W'), $withTax);
  242. $data[$week] = $turnover;
  243. }
  244. }
  245. else {
  246. foreach ($period as $date) {
  247. $month = $date->format('m/Y');
  248. $dataLabels[] = $month;
  249. $turnover = $this->getTurnover($producer, $date->format('Y-m'), $withTax);
  250. $data[$month] = $turnover;
  251. }
  252. }
  253. // création d'un tableau sans index car chart.js n'accepte pas les index
  254. $dataNoIndex = [];
  255. foreach ($data as $key => $val) {
  256. $dataNoIndex[] = round($val, 2);
  257. }
  258. return [
  259. 'labels' => $dataLabels,
  260. 'data' => $dataNoIndex
  261. ];
  262. }
  263. public function getSummaryAmountsToBeBilled(Producer $producer, string $label, int $numberOfMonths = 1, $context = 'list'): string
  264. {
  265. $text = '';
  266. $numMonthCurrent = date('m');
  267. $sumInvoicePrice = 0;
  268. if ($numberOfMonths == 1
  269. || ($numberOfMonths == 3 && (in_array($numMonthCurrent, [1, 4, 7, 10])))
  270. || ($numberOfMonths == 6 && (in_array($numMonthCurrent, [1, 7])))
  271. || $context == 'billing') {
  272. for ($i = 1; $i <= $numberOfMonths; $i++) {
  273. $timeMonth = strtotime('-' . $i . ' month');
  274. $month = date('Y-m', $timeMonth);
  275. $turnover = $this->getTurnover($producer, $month);
  276. if ($turnover) {
  277. if ($numberOfMonths > 1) {
  278. $text .= ucfirst(strftime('%B ', $timeMonth)) . ' ' . date('Y', $timeMonth) . ' : ';
  279. }
  280. $isBold = $this->producerSolver->isBillingTypeClassic($producer) && !$producer->option_billing_permanent_transfer;
  281. if ($isBold) $text .= '<strong>';
  282. $text .= $this->producerPriceRangeRepository->getAmountToBeBilledByTurnover($turnover, true);
  283. if ($isBold) $text .= '</strong>';
  284. $text .= ' (' . Price::format($turnover, 0) . ' CA)';
  285. $text .= '<br />';
  286. $sumInvoicePrice += $this->producerPriceRangeRepository->getAmountToBeBilledByTurnover($turnover, false);
  287. }
  288. }
  289. if (strlen($text)) {
  290. $text = '<i>' . $label . '</i>' . ' : <br />' . $text;
  291. }
  292. }
  293. if ($numberOfMonths > 1 && $sumInvoicePrice > 0) {
  294. $text .= '<br />Total : <strong>' . Price::format($sumInvoicePrice, 0) . '</strong>';
  295. }
  296. return $text;
  297. }
  298. public function getAmountToBillCurrentMonth()
  299. {
  300. $amountToBill = 0;
  301. $producersArray = $this->findProducersActive();
  302. foreach ($producersArray as $producer) {
  303. $amountToBill += $this->getAmountToBillCurrentMonthByProducer($producer);
  304. }
  305. return $amountToBill;
  306. }
  307. public function getAmountBilledLastMonth()
  308. {
  309. $amountBilled = 0;
  310. $producersArray = $this->findProducersActive();
  311. foreach ($producersArray as $producer) {
  312. $amountBilled += $this->getAmountBilledLastMonthByProducer($producer);
  313. }
  314. return $amountBilled;
  315. }
  316. public function getAmountToBillCurrentMonthByProducer(Producer $producer): float
  317. {
  318. return $this->getAmountToBeBilledByMonth($producer, date('Y-m'));
  319. }
  320. public function getAmountBilledLastMonthByProducer(Producer $producer): float
  321. {
  322. return $this->getAmountToBeBilledByMonth($producer, date('Y-m', strtotime('-1 month')));
  323. }
  324. public function getOnlinePaymentMinimumAmount(): float
  325. {
  326. $onlinePaymentMinimumAmount = $this->producerSolver->getConfig('option_online_payment_minimum_amount');
  327. if (!$onlinePaymentMinimumAmount) {
  328. $onlinePaymentMinimumAmount = Producer::ONLINE_PAYMENT_MINIMUM_AMOUNT_DEFAULT;
  329. }
  330. return $onlinePaymentMinimumAmount;
  331. }
  332. public function getNameProducer(User $user): string
  333. {
  334. $producer = $this->findOneProducerById($user->id_producer);
  335. return $producer->name;
  336. }
  337. public function isDocumentDisplayOrders(DocumentInterface $document)
  338. {
  339. return ($this->documentSolver->getClass($document) == 'Invoice') ?
  340. $this->producerSolver->getConfig('document_display_orders_invoice') :
  341. $this->producerSolver->getConfig('document_display_orders_delivery_note');
  342. }
  343. /**
  344. * Retourne le mode de fonctionnement de la cagnotte d'un point de vente.
  345. */
  346. public function getPointSaleCreditFunctioning(PointSale $pointSale): string
  347. {
  348. return strlen($pointSale->credit_functioning) > 0 ?
  349. $pointSale->credit_functioning :
  350. $this->producerSolver->getConfig('credit_functioning');
  351. }
  352. public function findProducersActive(string $orderByField = 'producer.name', string $orderByDirection = 'ASC')
  353. {
  354. return $this->queryProducersActive($orderByField, $orderByDirection)->find();
  355. }
  356. public function countProducersActiveWithTurnover(): int
  357. {
  358. $count = 0;
  359. $producersArray = $this->findProducersActive();
  360. foreach ($producersArray as $producer) {
  361. if ($this->getTurnoverByNumberMonths($producer, 3)) {
  362. $count++;
  363. }
  364. }
  365. return $count;
  366. }
  367. public function countCacheProducersActiveWithTurnover(): int
  368. {
  369. return \Yii::$app->cache->getOrSet('count_producers_active', function () {
  370. return $this->countProducersActiveWithTurnover();
  371. }, 60 * 60 * 24);
  372. }
  373. public function findProducers()
  374. {
  375. return $this->createQuery()->find();
  376. }
  377. public function findProducersWithTestimonials(): array
  378. {
  379. return $this->queryProducersActive()
  380. ->filterHasOptionTestimony()
  381. ->find();
  382. }
  383. public function findProducersWithTimeSaved(): array
  384. {
  385. return $this->queryProducersActive()
  386. ->filterHasOptionTimeSaved()
  387. ->find();
  388. }
  389. public function countProducersWithTimeSaved(): int
  390. {
  391. return count($this->findProducersWithTimeSaved());
  392. }
  393. public function getTimeSavedByProducersAverage(): int
  394. {
  395. $producersWithTimeSavedArray = $this->findProducersWithTimeSaved();
  396. $countProducersWithOptionTimeSaved = count($producersWithTimeSavedArray);
  397. if ($countProducersWithOptionTimeSaved) {
  398. $sumTimeSaved = 0;
  399. foreach ($producersWithTimeSavedArray as $producerWithTimeSaved) {
  400. $sumTimeSaved += $producerWithTimeSaved->option_time_saved;
  401. }
  402. return round($sumTimeSaved / $countProducersWithOptionTimeSaved);
  403. }
  404. return 0;
  405. }
  406. public function findOneProducerBySponsorshipCode(string $sponsorshipCode): ?Producer
  407. {
  408. return $this->createQuery()
  409. ->filterBySponsorshipCode($sponsorshipCode)
  410. ->findOne();
  411. }
  412. public function findProducersSponsorshipGodsons(Producer $producer): array
  413. {
  414. return $this->createQuery()
  415. ->filterBySponsoredBy($producer)
  416. ->find();
  417. }
  418. }